📝Clojure Product Development
up: 📂Clojure Development tags: 🏷Clojure
Clojureプロダクト開発で役立つライブラリとフレームワークまとめ.
必要に応じてトピックごとにリファクタリングすること.
- refs:
- 📝Clojure Web Development => Web関係はこっち
- 📝Clojure API Client Development => Client API関係はこっち
- 📝Clojure Style Rules & Conventions => コーディング規約や慣習はこっち
- 📝Clojure Architecture => 設計周り
Clojure: Backend Framework #
Clojure: Duct #
Integrant をベースにした拡張機能を提供(作者が同じ). Webフレームワークではなくもっと汎用的なもの.
💡 Javaからのアナロジー #
小さな構成のシステムをJavaで作成するならモジュールごとにクラス分割するが, Ductはクラスに初期化データとその方法を定義するためのコンストラクタを定義するようなものか? 責務を意識したクラス設計とはドメイン分割.
Clojure: Roll #
https://github.com/dimovich/roll
backend for Clojure. Ductよりもさらにシンプル, integrantがベース.
Clojure: 状態管理とシステム #
いろいろあるがナウいのはIntegrantかな?
状態管理ライブラリとは関数型パラダイム固有のものかな? Redux的な.
そもそもシステムとコンポーネントとは #
システムはそもそもアプリケーションが長い間起動しているときに必要となる考え. スクリプトやユーティリティの実行では必要ないのだ. それらは処理の終了がプログラムの終了でありリソースの開放である.
バックグラウンド実行をし続ける部分で構成されるアプリケーション, その部分が依存関係にあり初期化時に関係を構築するもの, これがシステム.
システム構築する部品=コンポーネントは単純化すれば, On/Offの操作によるStatefulなObjectである.
ref: Systems in Clojure
Clojure: Component #
Clojure: Integrant #
- refs.
データ駆動設計によるアプリケーションを作成するためのマイクロフレームワーク.
Dependency Ingection をClojureで実現.
設定データに対する初期化関数を定義でき, 設定データの定義から実体を生成.
Usage #
- integrant
- configuration map をもとに生成されるmicro-serviceの1つの単位.
- configuration map
- keyの定義をまずする. これは具体的な実装へと初期化される入力情報.
- Clojureのmapとして表現するかEDNファイルとして外部ファイルに定義する.
- configuration map同士は ig/ref で 参照することができる.
- ig/init-key で integrant serviceの初期化におけるデータと関数を定義.
- ig/halt-key!でintegrant serviceのinit-keyの定義を破棄する関数を定義.
- ig/initにconfiguration mapを渡すことで, 依存関係に従って integrant を初期化.
- init/halt!で 破棄.
(defmethod ig/init-key :handler/greet [_ {:keys [name]}]
(fn [_] (resp/response (str "Hello " name))))
- {:keys [name]}はClojure 分配束縛の記法.
- ref: 📝Destructuring
- keyに ::hogeみたいな 2つのコロンをみかける. ::hogeは :(namespace)/hogeの意味.
- REPLで評価するとわかる, reader syntax.
Usage: Integrant suspend/resume #
https://github.com/weavejester/integrant#suspending-and-resuming
Integrantはinitとhalt, つまりシステムの開始と終了の機能を提供する.
suspend/resumeは主に 開発用 である. そして使いこなすにはatomとdelayをつかうというひと工夫を加える.
Integrantの考え方としてnamespaceにatomをbindingしない. その代わりに init-keyの中で atomを宣言して返り値のmapにbindingする.
References #
- Enter Integrant: A Micro-framework for Data-Driven Architecture (James Reeves, 2017) - YouTube
- 作者によるプレゼンテーション動画.
- Integrant入門(1) - Integrantの基本 - ayato-p
- はじめてのDuct - Uzabase Tech
- integrantについても書かれてる.
💡考察: Integrantで状態を管理するということ(as State Management) #
Clojureの世界では, 普通は状態をatomで管理する.
Integrantを導入することで, 各namespaceに散らばるatomで宣言された状態をsystemというひとつの状態に紐づけてまとめることができる. そしてこのツリー構造で状態を管理するからこそシステムの停止や再起動が用意にできる.
逆に言うと, Integrantを利用するということは, namespaceでatomを宣言しないということ.
Integrant: how to store and access the running system? : Clojure
Systems in Integrant are intended to be autonomous.
Anyway, my point is that it seems to me Integrant still requires a whole app buy-in in the sense that, unlike with Mount, you have to thread all your state through a single entry-point.
systemはthreadで動作する再帰プロセス. しかしこれはIntegrantと言うよりも関数型プログラミングのイディオム.
Only constants should be global.
定数のみが参照可能であり状態は隠されているという考え(debug除く).
💡考察: Java Command Patternからのアナロジー #
クラスというものを単なる抽象データ構造と捉えると, クラスには属性としての値と関数値の集合であり, オブジェクトとはそれをメモリ上に領域確保した状態.
ig/init-keyでやっていることは値とその初期化関数のpairのbindingであり, ig/init-keyで定義したpairの集合をig/initでまとめて初期化している.
そうすると, ig/init-keyで初期化したそれぞれのオブジェクトを1つのオブジェクトに bindingして管理しているようにもみえる. 管理ということで, suspend, resume, haltはオブジェクトを Command Pattern で扱うようなものとして捉えれば納得がいく.
(アナロジーとして類推しただけで実装を読んではない…後で読む).
💡考察: Integrant Rationale cf. Component #
Clojure Component の代替を意識して, とくにComponentが依存関係をプログラム内(Clojure Source Code)で管理するが, IntegrantはEDNで管理するところがこだわりポイント.
すなわちIntegrantはClojure MapでもEDNでもどちらでも構成定義できるが, 設計動機からいえばEDNつかえよ!ということかな?
💡考察: Component/MountとIntegrantの決定的違いはOOP vs FP #
ComponentやMountを使ったことがないので以下はリンク先からの理解.
Integrant: an alternative to Component and Mount : Clojure
ComponentやMountは状態をグローバルに参照することができるので, 関数の引数としてもらう必要がない.
Integrantは状態がライブラリの中に隠されていて自由に参照できない. そのためその状態に対する操作は関数の引数としてもらって変化した値を返すように書く. またはhandlerの定義として状態とそれに対する操作を1つにbindingする.
Webフレームワークならたくさんのサンプルを見ながら自然とこの初期化で状態と関数をhandlerとしてbindingするパターンに従えばいいものの, webとは関係なく単にintegrantを使おうとしたとき, ベストプラクティスがないので自分の流儀で実装しがち, 本質を考えよう.
初期化時に自前でnamespaceにatomに保存しておく方法はそもそもフレームワークで状態を管理する考えに反するアンチパターン.
Integrantは関数型プログラミング(FP)の考えに近い. 一方Componentの考えはクラスやOOPに近い.
とくにFPでシステムを構築すると冪等性を獲得することができ, これが開発時にとくに役に立つ(cf. Reloaded Workflow). Componentは自分が初期化済みかどうかはComponent自身しかわからない.
💡IntegrantとRing Handlerの2つのパターン #
- refs:
2つのパターンがある.
- すべてのRingハンドラーをコンポーネントとして扱う.
- ルーター部分までをコンポーネントにして、Ringハンドラーはただの関数として扱う.
Component/MountとIntegrantの決定的違いはOOP vs FP の議論に似ている.
Clojure: ロギング(Logging) #
Javaの資産を使うか否かが採用のポイントかな?
timbreはpure clojureではあるが, やはり設定例やノウハウがGoogle検索で見つかるのは, Javaライブラリが多い(logback).
Javaのロギングライブラリは歴史がある. ログはViewerがよいとワクワクするからな.
clojure.tools.logging #
clojure tools.logging libaryでJavaの資産を活用.
- ref:
Clojure: timbre #
Pure Clojure/Script logging library.
GitHub - ptaoussanis/timbre: Pure Clojure/Script logging library
slf4j-timbre をつかうとJavaのロギングライブラリと連携可能.
日本の時刻設定例:
(def timbre-config
{:timestamp-opts
{:pattern "yyyy-MM-dd HH:mm:ss,SSS"
:locale (java.util.Locale. "ja_JP")
:tiemzone (java.util.TimeZone/getTimeZone "Asia/Tokyo")}})
(timbre/merge-config! timbre-config)
また現実的な問題点として, timbreはpure clojureではあるものの, 3rd party libraryがjavaのライブラリに依存していると,その制御をtimbreからはできない(Javaから独立していることが利点なので). そのため結局 logback.xmlを書かないといけないかもしれない.
https://github.com/ptaoussanis/timbre/issues/138
(なんかcider-nreplとの相性なのか CIDEE環境でerrorが動作しない. これはstderrの扱いかもしれない).
💡 tips: Jetty サーバのログを黙らせる #
jetty serverを起動したりclj-httpを使うとREPLを侵食する問題.
以下はlogback.xmlを設定する例.
- Pedestalのデバッグログがうるさいので、静かにした - ayato-p
- Basic logging in Clojure web service not appearing on console - Stack Overflow
timbre
References #
- The Clojure Toolbox
- 逆引きのClojure Libraryまとめページ.