[連載]Clojureで作るAPIの7記事目です。
前回の記事はこちらです。
この記事ではアプリケーションの設定をClojureのソースではない設定ファイルで管理できるようにしていきます。
なぜ設定ファイルで管理する必要があるのか
実際のアプリケーションでは、開発環境・ステージング環境・本番環境と複数の環境で動くようにしなければいけません。 DBの接続先や外部のAPIキーなど、動作環境によって切り替えたい値は複数あります。
設定値は固定値だけでなく環境変数から読み込んだものであったりするでしょうが、設定値を各所に書き散らかしてしまうと保守性が下がるのでそれらを一箇所にまとめて管理したいです。
今回はそういったことを実現していきます。
aeroを追加
この目的のためにaeroというライブラリがあります。こちらを使っていきましょう。
まずライブラリを deps.edn
に追加します。
;; ./deps.edn {:paths ["src" "resources"] ;; resourcesを追加 :deps {org.clojure/clojure {:mvn/version "1.11.1"} info.sunng/ring-jetty9-adapter {:mvn/version "0.17.6" :exclusions [org.slf4j/slf4j-api]} spootnik/unilog {:mvn/version "0.7.30"} com.stuartsierra/component {:mvn/version "1.1.0"} ;; ライブラリを追加 aero/aero {:mvn/version "1.1.6"}} :aliases {:dev {:extra-paths ["dev"]} :build {:deps {io.github.clojure/tools.build {:git/tag "v0.8.2" :git/sha "ba1a2bf"}} :ns-default build}}}
前回の記事でJARファイルにビルドできるようにしました。 多くのケースでJARをビルドした環境とそれを実行する環境とは異なるはずです。 ビルドした環境にはあるが実行環境にないファイルをビルドしたJARアプリケーションから参照したい場合は、そのファイルがJARに含まれるようにする必要があります。
:paths
にresourcesを追加すると、resources下のファイルがJARに含まれるようになります。
その上で設定ファイルを ./resources/config.edn
というパスに配置することで実行環境でも設定ファイルを参照できるようにします。
設定ファイルの追加
./resouces/config.edn
に設定をaeroの作法に従って書いていきます。
;; ./resources/config.edn {:server {:opts {:host "localhost" :port #long #profile {:default 5000 :dev 8000} :join? false}}}
ひとまず前回までの記事で立ち上げたWebサーバーの設定だけ追加しています。
ほとんど単なるClojureのMapですが、2つだけタグリテラルを使っています。
#long
は基本的に戻り値はStringであるところをlong型でパースして取得することを可能にします。他には #double
や #keyword
などがあります。
#profile
はprofileがMapのキーに一致する値を返します。profileはconfig.ednをパースする関数を呼び出すときに引数として与えます。この例だと :profile
が :dev
なら8000、それ以外なら5000ということです。
これ以外にもタグリテラルはあります。ここでは解説しませんが、今後新しいものが登場するたびに説明するのでご安心ください。
設定の読み込み
cljapi.config/read-config
に設定を読み込む関数を定義します。
;; ./src/cljapi/config.clj (ns cljapi.config (:require [aero.core :as aero] [clojure.java.io :as io])) (defn read-config [profile] {:pre [(contains? #{:dev :prod :test} profile)]} (-> (io/resource "config.edn") (aero/read-config {:profile profile}) (assoc :profile profile)))
:pre
は初登場ですが、これは関数の事前条件をチェックするために使います。上記の例だと引数profileが、:dev, :prod, :testのいずれかでなければ AssertionError
例外が投げられます。
io/resource
はクラスパスに含まれるパスの一致するファイルから java.net.URLを取得する関数です。先ほどconfig.ednはresources下に配置し、resourcesをdeps.ednで :paths に指定してあるのでURLが取得できるということです。
REPLで確かめてみましょう。
Systemとの統合
最後にSystemと統合し、開発環境では :dev profileで、本番環境では :prod profileで起動できるようにします。
まず cljapi.system
を次のようにします。
;; ./src/cljapi/system.clj (ns cljapi.system (:require [cljapi.component.handler :as c.handler] [cljapi.component.server :as c.server] [cljapi.config :as config] [com.stuartsierra.component :as component])) (defn- new-system [config] (component/system-map :handler (c.handler/map->Handler {}) :server (component/using (c.server/map->Jetty9Server (:server config)) [:handler]))) (defn start [profile] (let [config (config/read-config profile) system (new-system config)] (component/start system))) (defn stop [system] (component/stop system))
これまで c.server/map->Jetty9Server
に直接Mapを与えていたところをconfig.ednから読み込んだ設定を使うようにしています。
開発用の user
も修正します。
;; ./dev/user.clj (ns user (:require [cljapi.system :as system])) (defonce system (atom nil)) (defn start [] (reset! system (system/start :dev))) ;; startに :dev を与える修正 (defn stop [] (when @system (reset! system (system/stop @system)))) (defn go [] (stop) (start))
ここまで修正ができたら次の2つを確かめてみましょう。
- REPLを立ち上げて
(go)
を評価してcurl http://localhost:8000
を実行するとHello, Clojure API
と返ってくる make build
してjava -jar target/cljapi.jar
を実行してcurl http://localhost:5000
を実行するとHello, Clojure API
と返ってくる
profileによって異なる設定が反映されていることが確認できました。
おわりに
ここまでで、設定ファイルをソースコードの外に配置し、コンテキストによって異なる設定でアプリケーションを起動できるようになりました。
コードはGitHubのリポジトリに上げてあります。 07_設定をednで管理するというタグからご覧ください。
次はロギングの設定を行なっていきます。
トヨクモでは一緒に働いてくれる技術が好きなエンジニアを募集しております。