2022年4月24日日曜日

pedestalでコード変更時に自動更新


背景

pedestalとはclojureというプログラミング言語のサーバーライブラリの1つです。
コードを変更したら再起動せずとも自動でコードを読み直してくれる設定を把握するのに時間がかかったので、備忘録を兼ねて内容を記事に残します。

使ったもの

  • clojure cli 1.10.1
  • pedestalを利用したプロジェクト
    この記事で紹介するコードは下記のリポジトリで公開しています。
    clj-pedestal-reload-practice

自動更新適用前

差分を分かりやすくするため自動更新適用前のコードを共有します。
portが3000番のトップページを開くと「hello world」と表示されるpedestalのサーバーです。

deps.edn
{:deps {org.clojure/clojure {:mvn/version "1.10.3"}
io.pedestal/pedestal.service {:mvn/version "0.5.10"}
io.pedestal/pedestal.jetty {:mvn/version "0.5.10"}}
:aliases
{:run-m {:main-opts ["-m" "asuki.clj-pedestal-reload-practice"]}}

src/asuki/clj_pedestal_reload_practice.clj
(ns asuki.clj-pedestal-reload-practice
(:gen-class)
(:require [io.pedestal.http :as http]))

(defn handler-top [req]
{:status 200
:body "hello world"})

(def main-route
#{["/" :get handler-top :route-name :top]})

(defn start-server []
(let [host "0.0.0.0"
port 3000]
(println (format "starting server as http://%s:%d" host port))
(-> {::http/routes main-route
::http/port 3000
::http/host host
::http/type :jetty}
http/create-server
http/start)))

(defn -main
[& args]
(start-server))

下記のコマンドで起動できます。
(deps.ednに記述したmain関数を実行するエイリアスを通して起動します。)
clj -M:run-m

ブラウザで開くと期待通りに表示されました。


この状態だと自動更新は適用されていないため、hello worldの文字を書き換えを反映するには起動コマンドの停止と再実行が必要になります。

自動更新適用後

適用後のコードの全体を共有してから要所を解説します。
変更内容は下記のページを参考にしています。

Watch and auto reload a Clojure pedestal service on source change

deps.edn
{:deps {org.clojure/clojure {:mvn/version "1.10.3"}
io.pedestal/pedestal.service {:mvn/version "0.5.10"}
io.pedestal/pedestal.jetty {:mvn/version "0.5.10"}
ns-tracker/ns-tracker {:mvn/version "0.4.0"}}
:aliases
{:run-m {:main-opts ["-m" "asuki.clj-pedestal-reload-practice"]}
:dev {:ns-default asuki.clj-pedestal-reload-practice
:exec-fn run-dev}}

src/asuki/clj_pedestal_reload_practice.clj
(ns asuki.clj-pedestal-reload-practice
(:gen-class)
(:require [io.pedestal.http :as http]
[io.pedestal.http.route :as route]
[ns-tracker.core :refer [ns-tracker]]))

(defn handler-top [req]
{:status 200
:body "hello world"})

(def main-route
#{["/" :get handler-top :route-name :top]})

(def watched-namespaces
(ns-tracker "src"))

;; define target-route out of function to relodable
(def target-route main-route)

(defn watched-target-route []
(doseq [ns-sym (watched-namespaces)]
(require ns-sym :reload))
(route/expand-routes target-route))

(defn start-server [& args]
(let [host "0.0.0.0"
port 3000
watch (when (some? args) (.contains args :watch))]
(println (format "starting server as http://%s:%d" host port))
(when watch (println "with watching"))
(-> {::http/routes (if watch watched-target-route target-route)
::http/port 3000
::http/host host
::http/type :jetty}
http/create-server
http/start)))

(defn run-dev [& args]
(start-server :watch))

(defn -main
[& args]
(start-server))

下記のコマンドでmain関数を通して起動できます。
clj -X:dev

ブラウザで開くと期待通りに表示されます。


handler-topを書き換えてブラウザを更新してみます。
(defn handler-top [req]
{:status 200
:body "hello world with auto reloading"})


cljのプロセスを再起動しなくても変更内容が反映されました。

差分解説

ns-reloadというライブラリを追加しています。
このライブラリが変更監視をしてくれるようです。
{:deps {;; other deps
ns-tracker/ns-tracker {:mvn/version "0.4.0"}}}

run-devという関数で起動するためのaliasを定義しています。
{:aliases
{;; other aliases
:dev {:ns-default asuki.clj-pedestal-reload-practice
:exec-fn run-dev}}

ns-trackerを取り込み、srcを監視対象のディレクトリとして定義しています。
(ns asuki.clj-pedestal-reload-practice
(:require [ns-tracker.core :refer [ns-tracker]]))

(def watched-namespaces
(ns-tracker "src"))

通常版のrouteと監視機能が付いたrouteを定義します。
注意点として、target-routeをstart-server関数内でletの変数として定義すると、読み直し対象の範囲外となってしまいます。
そのため、routeはreloadで更新可能な定義をするのが良いです。
;; define target-route out of function to relodable
(def target-route main-route)

(defn watched-target-route []
(doseq [ns-sym (watched-namespaces)]
(require ns-sym :reload))
(route/expand-routes target-route))

pedestalのrouteとして渡す物を条件によって変えます。
監視したい場合はwatched-target-route、監視不要な場合はtarget-routeを渡しています。
start-serverに:watchが引数で渡されている場合は監視する処理にしました。
(defn start-server [& args]
(let [;; other variables
watch (when (some? args) (.contains args :watch))]
(println (format "starting server as http://%s:%d" host port))
(when watch (println "with watching"))
(-> {::http/routes (if watch watched-target-route target-route)
;; other settings
}
http/create-server
http/start)))

run-dev関数で:watchを引数で渡しつつstart-serverを実行します。
(defn run-dev [& args]
(start-server :watch))

上記の変更により、run-dev関数を実行するとsrcの更新を監視するpedestalのサーバーが起動します。

終わり

cljプロセスを再起動しなくてもコードの変更をpedestalのサーバーに反映できました。

この方法が分かるまで変更する度サーバーの再起動のため数十秒の待ち時間が発生していましたが、自動更新できるようになってからブラウザの更新やリクエストの再送で変更を繁栄出来るようになり、効率が上がりました。

参考

Clojureサービス開発ライブラリPedestal入門
pedestal/samples/auto-reload-server/dev/dev.clj

本文で紹介済みのリンク
Watch and auto reload a Clojure pedestal service on source change
pedestal
clj-pedestal-reload-practice

0 件のコメント :