Clojure 앱에서 환경변수를 사용할 때는 어떻게 해야 할지 생각해봤다. Node.js에서는 dotenv 라이브러리를 사용해서 중요한 변수들을 코드로부터 분리하고 노출되는 것을 막을 수 있었는데 Clojure에서도 당연히 그런 라이브러리가 있을 거라는 생각에 검색을 해보니 weavejester/environ을 많이 쓰는 것 같았다. 그런데 좀 더 살펴보니 yogthos/config 라는 것도 괜찮을 것 같아 사용해봤다. 


Node.js에서 dotenv 사용

https://iamcool.tistory.com/7?category=779855


weavejester/environ

https://github.com/weavejester/environ


yogthos/config

https://github.com/yogthos/config


yogthos/config 라이브러리를 사용하면 jar 외부의 config.edn 파일을 사용할 수도 있고 jar 파일 내부에 config.edn, prod.edn 파일을 포함시킬 수도 있다. 이번에는 두 번째 방법을 이용해 런타임에서 환경변수에 접근했다. 


프로젝트 생성

lein을 이용해서 새로운 프로젝트를 생성한다.

lein new app env-app


라이브러리 및 프로파일 추가

아래와 같이 yogthos/config 라이브러리를 project.clj 파일에 추가한다. 그리고 dev, prod에서 각각 사용하게 될 config.edn 파일의 위치를 profiles에 추가한다. 

1
2
3
4
5
6
7
8
9
10
11
12
(defproject env-app "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.9.0"]
                 [yogthos/config "1.1.1"]]
  :main ^:skip-aot env-app.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot :all}
             :prod {:resource-paths ["config/prod"]}
             :dev  {:resource-paths ["config/dev"]}})
cs

config 폴더 생성

project.clj 파일의 profiles에 추가한 config.edn 파일을 생성하기 위해 아래와 같이 폴더 및 파일들을 생성한다. 

config

  - dev

    - config.edn

  - prod

    - config.edn


config.edn 파일 생성

config 폴더에 생성한 config.edn 파일에 아래와 같은 내용을 입력한다. 파일이 로드되면 :runtime 키워드로 접근할 수 있으며 MySQL, Redis 서버 정보를 포함하고 있다. 

1
2
3
4
5
6
7
8
9
{:runtime {:name "dev"
           :db {:dbtype "mysql"
                :host "localhost"
                :port 3306
                :user "user"
                :password "password"
                :dbname "dbname"}
           :redis {:host "localhost"
                   :port 6379}}}
cs
config/dev/config.den 파일


1
2
3
4
5
6
7
8
9
{:runtime {:name "prod"
           :db {:dbtype "mysql"
                :host "prod_host"
                :port "prod_port"
                :user "prod_user"
                :password "prod_password"
                :dbname "prod_dbname"}
           :redis {:host "prod_host"
                   :port "prod_port"}}}
cs

config/prod/config.edn  파일


core.clj  파일 변경

앱의 entry point인 core.clj 파일에 로드한 환경변수를 출력할 수 있는 코드를 추가한다. 

1
2
3
4
5
6
7
8
9
10
11
(ns env-app.core
  (:gen-class)
  (:require [config.core :refer [env]]))
 
(defn -main [& args]
  (let [{name :name
         db :db
         redis :redis} (:runtime env {})]
    (println name "runtime variables for MySQL:", db)
    (println name "runtime variables for Redis:", redis))
  (println "App start!!"))
cs

yogthos/config 라이브러리의 core로부터 불러온 env를 통해서 project.clj 파일의 profiles에서 정의한 파일로부터 로드한 환경변수에 접근할 수 있는데, config에서 정의한 값을 읽기 위해 :runtime을 키워드를 이용한다. 그리고 MySQL, Redis 값을 출력했다. 


테스트

앱을 실행하여 dev 환경변수를 출력한다. 

$ lein deps

$ lein run

dev runtime variables for MySQL: {:dbtype mysql, :host localhost, :port 3306, :user user, :password password, :dbname dbname}

dev runtime variables for Redis: {:host localhost, :port 6379}


App start!!


prod 환경변수를 출력하기 위해 아래 명령을 실행한다. 

lein with-profile prod run

prod runtime variables for MySQL: {:dbtype mysql, :host prod_host, :port prod_port, :user prod_user, :password prod_password, :dbname prod_dbname}

prod runtime variables for Redis: {:host prod_host, :port prod_port}


App start!!


이 예제에서는 jar 파일 내부에 config.edn 파일을 추가했는데, 경우에 따라 외부로부터 입력을 받아야 할 필요가 있을 수 있다. 이 때에는 yogthos/config의 다른 예를 살펴보면 좋겠다. 


이 방식으로 CircleCI 통해서 Google Cloud에 앱을 올려봤다. 로컬이나 개발서버에서는 dev/config.edn 파일을 읽도록 했고 Google Cloud에 릴리즈할 때는 CircleCI 설정 파일에서 jar 파일을 만들 때 lein with-profile prod uberjar 커맨드를 실행하여 prod 환경변수를 읽도록 했다. 


소스코드

이것도 간단한 예제이지만 아래에 전체 소스코드를 올려놓았다. 


https://github.com/ksleeq21/clojure-env-example



Posted by 코딩새싹
,