カマイルカ🐬)です(最近、トルコ語を学ぶのが楽しい)。Easyが全文書き起こし記事になったりと、個人的にもこれまでとはまた違った形でClojure/Lispコミュニティに関わることができたと感じる一年でした。Conquest(?)が着々と進んでいます。APIを開発する例をご紹介しました。そこでは敢えて1ファイル(1名前空間)にすべてのコードを収めていましたが、本記事では、より快適に本格的に開発しやすく、今後の拡張をスムーズにするためにリファクタリングを試みましょう。構成を整理するにあたり、全体的にDuctフレームワークのスタイルを参考にしています。(初回執筆時点)まずは、初期実装から時間も経過しているので、依存ライブラリの最新版を取り込んでみましょう。Clojureは言語本体と標準ライブラリの後方互換性が非常に重視されていて、コミュニティ全体としても特によく使われる主要なライブラリは破壊的変更なく拡張されていくことが多いため、たいていの場合は素直に最新版に更新することができるでしょう(もちろん念のためCHANGELOGなどを確認しましょう)。多機能の大きなライブラリ(フレームワーク)よりも単機能に特化した小さなライブラリを組み合わせることを好む傾向が強く、小さく安定したライブラリは良い意味で枯れているために数年以上の更新がないこともあります。Leiningenの場合lein-ancientというLeiningenプラグインを利用すると、更新可能な依存ライブラリを確認したり自動更新したりすることが簡単にできます。"0.4.0"すべての依存ライブラリを最新化すると、以下のような出力に変わります。CLIの場合Depotというライブラリを利用することで依存ライブラリの最新版の確認や更新ができます。いうエイリアスを設定してあります。"0.5.0"}すべての依存ライブラリを最新化すると、以下のような出力に変わります。前回の記事ですでにIntegrantを導入しているので、ここではIntegrant-REPLというライブラリを追加してREPLからより扱いやすい形に整えましょう。パスに追加してみました。CLIでは書き方に少し差がありますが、ほぼ同等の設定が可能です。したので、その名前空間から開発時に便利な関数や変数にアクセスできるようにします。ロード時にエラーが発生した場合にREPL自体の起動に失敗してしまうことがあります。dev)に切り替える仕組みを用意します。名前空間を起点に様々な操作をすることができます。名前空間には必要に応じてrequireするlibを追加したりユーティリティ関数を定義したりすると良いでしょう。REPLを起動してIntegrant-REPLによるAPIの起動/停止を確かめてみましょう。すると、ログ出力からも分かるようにAPIサーバが起動(または再起動)します。いう2つだけ覚えておけば実用上十分です。システムマップが自動的に束縛されます。APIが動作していることが確認できます。応答からAPIサーバが停止していることが確認できます。系関数呼び出し)の前後に任意の関数をフックすることができます。呼び出しを代用することが可能になります。本格的に既存コードに手を入れる前に、既存の振る舞いを保証するためのテストを整備しましょう。APIに対するE2E的なテストを用意し、静的解析ツールとともにCIで自動実行できるようにします。依存ライブラリと設定の追加Clojureで定番のHTTPクライアントclj-http、テスト結果の出力を見やすくするhumane-test-outputを開発時の依存ライブラリに追加し、またLeinigenプラグインとしてリンターのEastwood、テストカバレッジ計測のcloverage、イディオムチェッカーのkibitを追加しています。いうエイリアスも設定してみました。CLIでは一部使えない(使うのが難しそう)なツールもありますが、機能的に近い設定を以下のように書いてみました。コマンドラインから手軽に実行できるようにしてみました。これらの静的解析ツールはエディタ/IDEで自動実行するように設定しておくと非常に便利です。必要なライブラリと設定は追加したので早速テストコードを書いていきたいところですが、あらかじめ今回書こうとするテストコードでよくあるパターンを関数やマクロとして定義しておくと便利です。関数を用意してみました。関数)が、テスト時固有の設定マップを差分だけ別途定義しておき、Meta-Mergeのようなライブラリで賢くマージさせるような方法も考えられます。APIに対するテストを記述することができます。-M:dev:test:cloverage)でカバレッジ計測付きのテスト実行が可能です。CLIの起動のオーバーヘッドを考えると、開発時に起動中のREPLから素早くテストが実行できると非常に捗ります。関数を用意してみても良いかもしれません(さらにEftestのようなサードパーティのテストランナーを導入する案もあります)。最後にテストと静的解析ツールをCIで自動実行させるための設定を追加します。CircleCI向けに以下のように設定することができるでしょう。(初回執筆時点)テストケースとCI環境を用意して高いテストカバレッジも確認できたので、いよいよ本格的なリファクタリングに取り組んでみましょう。前回の記事における初期実装では1名前空間にすべてを定義していましたが、アプリケーション内での責務に合う形で名前空間を分割し、Integrantの設定マップもこのタイミングでednファイルとして外部化します。個々の独立した名前空間(モジュール)に分けることで単純にコードの見通しが良くなるのはもちろん、それぞれの名前空間を凝集性高く疎結合に構成していくことで変更の影響を局所化しながら拡張しやすいクリーンなアーキテクチャの実現にも繋がります。アプリケーションのエントリーポイントとしての責務に特化させ、残る責務に対応する形で以下のように名前空間を分割してみます。して外部ファイル化します。Integrantのコンポーネント名)が変わることに注意が必要です(Integrantの名前空間自動ロード機能を活かすには、コンポーネントを定義した名前空間に対応するキー名を採用するのがオススメです)。false}}}システム起動方法の変更システムの設定マップをednファイルに抽出したことにより、Integrantを介したシステムの起動方法も自ずと変更することになります。システムを起動するのは、本番アプリケーションのためのメインエントリーポイント、開発時のREPL、テスト時のテスト支援ユーティリティの3箇所なので、それぞれに修正が必要です。関数だけが残ることになります。名前空間に設定します。読み込んだ内容をシステムの設定マップとして利用します。minimal-api-clj.core)でAPIをコマンドライン起動したりすることができます。可変状態を実現していること、内部に保持されているマップの構造など)を知りすぎている極めて密結合な状態であることに気づきます。DIP)に従う)ため、Clojureのプロトコル(protocol)を活用してDBアクセスのインターフェースと実装を分離しましょう。Ductフレームワークではこの種の外部サービス/リソースとの境界を定義するようなプロトコルを"boundary"(バウンダリ)と呼んでいるため、ここでもその命名を踏襲します。いう名前空間を新たに用意しましょう。いう名前でレコードを定義すると自動生成されるコンストラクタ関数です)。キーに持たせるように変更しています。して定義しておきます。場合の実装を与えます。いう実装が呼び出されることを意味します。id)))DB情報のリクエストマップへの注入(DI)インメモリDBからTODOデータにアクセスする関数は実装できたので、あとはハンドラ関数から呼び出せるようにする必要があります。値を渡すことができれば良いので、ここではミドルウェアを利用してハンドラ関数の入力であるリクエストマップに注入することにします。コンポーネントを参照させます。いうキーでリクエストマップに追加します。経由でリクエストマップに差し込みます。プロトコル関数(DBバウンダリ関数)の動作を確認しておきましょう。用意しておくと便利かもしれません。関数を利用してDBバウンダリ関数を試してみると、期待通りに動作していることが確認できます。プロトコルの関数を利用するだけです。プロトコルというインターフェースに依存するのみでインメモリDBという実装の詳細に対する依存がなくなります。ハンドラ関数は、TODOデータがインメモリDBにあるのか、RDBにあるのか、ファイルにあるのか、外部APIに由来するのかさえ知らない状態にすることができたわけです。マクロとインメモリDBのデータ構造の変更を取り込めば、今まで通りすべてのテストがパスするはずです。(初回執筆時点)今回のAPIでは(インメモリ)DBに対するアクセスがビジネスロジックとして非常に重要ですが、DBアクセス関数の入出力データがどのようなものであるか必ずしも明らかでないという問題が残っています。開発時やテスト時に想定しない入出力に対して分かりやすい形でfail-fastになるように、clojure.specでDBアクセス関数を保護することにしましょう。フィールドを持つマップであるとspec定義します。値とするマップを返す関数として仕様を記述することができます。返す関数として仕様を表現できるでしょう。プロトコル関数の入出力の仕様をclojure.specで定義することができます。開発環境への組み込みと動作確認specが定義できたので、開発環境でREPLからspecのチェックが動作することを確かめてみましょう。