株式会社ユーザベースの技術チームブログです。Duct2018-04-03はじめてのDuctTweetSPEEDA開発の中村です。#2で発表した「実践Duct(仮)」です。ClojureのサーバサイドフレームワークDuctを業務で使って学んだことを紹介します。勉強会で発表した資料はこちらです。サーバサイドでIntegrantを使って感じた課題についてお話しします。次に,課題に役立つDuctのmoduleのしくみと作り方を説明します。想定読者は,Clojureを書いたことがあってDuctを使ったことがない方です。目次IntegrantのつかいかたIntegrantなせかいIntegrantでこまることDuctのmoduleDuctでこまることDuctによるコンフィグレーションマップの展開Ductのmoduleのつくりかた参考資料IntegrantのつかいかたIntegrantは,アプリケーションのモジュール構成をmapやednのようなデータで表現するためのDIフレームワークの一種です。Integrantは,アプリケーション起動時にデータが示すモジュールの依存関係を解決し,アプリケーションを初期化します。Integrantのドキュメントでは,データで構造が表現されたアプリケーションをデータドリブンアーキテクチャと呼んでいます。例題を通してIntegrantのイメージをつかみましょう。とりあげる例題は,ヘルスチェックを行うringサーバのハンドラを初期化する処理です。get-healthは引数に与えられたURL先のステータスを問い合わせる関数と考えてください。3000}))なんてことはないプレーンなコードですが,変数や関数のスコープが必要以上に大きいという問題があります。mainはhandlerに,handlerはendpointに依存しており,本来mainからendpointが見える必要はありません。mainとendpointがグローバルなスコープを持っているため,お互いに参照できるようになっています。関数のスコープは呼び出される関数だけから見える大きさで十分です。Integrantは,このスコープと依存解決の問題をクロージャで解決します。次の2つのコードは上のコードをIntegrantで再実装したものです。この2つのコードは,上段がdefmethodで囲まれたモジュールの実装で,下段がモジュール間の依存関係の定義するednです。依存するオブジェクトをdispatchの引数として関数に与えることで,Integrantはスコープを小さくします。3000}}この依存関係を宣言するednが,コンフィグレーションマップと呼ばれるアプリケーションの構造を定義するデータです。マップの最上位の各キーはそれぞれモジュールであり,キーの値はモジュールの初期化に必要な別モジュールや値です。#ig/refを使えば別のキーワードを参照できます。ここでは,ハンドラはURLに依存し,jettyサーバはハンドラとポート番号に依存するという関係があります。アプリケーションを初期化するには,コンフィグレーションマップをintegrant.core/initに渡します。"config.edn")))initは,最上位のキーの値がマルチメソッドの返り値に置き換わったマップを返します。上の例であれば,initは,マップにある:ebisu/handlerがget-healthを本体で呼び出す高階関数に置き換わったマップを返します。余談ですが,IntegrantもDuctもClojureのライブラリをたくさん作られているweavejester先生の作品です。本稿ではDuctより先にIntengrantについて説明しますが,IntegrantはDuctより後に生まれたフレームワークです。Integrantが担っているDuctの機能には,もともとComponentが使われていました。後に,先生は何かに不満を覚えたのか,Componentを使っていた部分をIntegrantに置き換えました。Integrantなせかいこれまでみたように,Integrantは,モジュールの実装(defmethodの本体)とアプリケーションの構造(コンフィグレーションマップ)にプログラムを分離します。次は,分割の後ろにある考え方を説明します。Integrantは,Arachneから影響を受けて作られたため,Arachneの考えを受け継いでいます。2016での作者によるプレゼンテーションを観るとよいでしょう。プレゼン前半のテーマは,フレームワークとライブラリの違いです。フレームワークはアプリケーションの構造を規定する,という主張でした。テーマの後ろには制御の反転やハリウッドの法則があります。IntegrantはArachneのアイデアをベースにしているので,Integrantにも,アプリケーションの構造を定義する役割があります。モジュールを指すシグネチャがあれば,アプリケーションの構造(モジュールの依存関係)を宣言できます。モジュールの実装はアプリケーションを実際に起動するまでいりません。このように,モジュールの実装とモジュール間の依存関係は別の関心事とみなせることができます。それゆえに,関心の分離にしたがって,Integrantは,モジュールの実装とモジュールの依存関係をマルチメソッドとコンフィグレーションマップに分けています。Integrantでこまること弊社では,Clojureで作るものは主にWebアプリケーションです。Webアプリケーションには,ルーティングやDBアクセスなどドメインを問わず実装すべきものがあります。フルスタック系のWebフレームワークであればこれらをサポートしているものもありますが,Integrantにはありません。使いたい機能があれば,自分でそれをIntegrantのモジュールにする必要があります。DuctのmoduleDuctはIntegrantをベースとするサーバサイドのフレームワークです。module1と呼ばれる形式でアプリケーションを問わずよく使われる機能を提供しています。moduleを使えば,Integrantで必要だったボイラープレートコードを減らせることができます。有名なmoduleはductのリポジトリのREADMEで紹介されています。github.comDuctでこまること上のサイトを見た方は気づいたかと思いますが,執筆時点においてmoduleの数は多くありません。moduleを使えばボイラープレートコードを減らすことができますが,なければどうしようもありません。アプリケーション間で再利用したいコードがあり,その機能を担うmoduleがないのであれば,moduleを自分で作るしかないでしょう。ところが,module同様に,moduleを作るための参考資料もまた少ないのです。いざmoduleを自作してみようとしたところ,情報が少なくて困りました。そこで,moduleの作成する上で知っておくと役立つmoduleの振る舞いと作り方について紹介します。追加したエントリに対応する実装(マルチメソッド)を提供したりすることです。init-keyマルチメソッドを呼び出すという2手順に分解できます。stepでコンフィグレーションマップが展開されます。moduleが,コンフィグレーションマップを書き換える過程を追ってみましょう。moduleは,自分たちで作るモジュールと同様,Integrantのキーワードにすぎません。moduleの場合は:duct.module/loggingです。stepまで進めています。stepまで進めます。configを評価すると展開後のコンフィグレーションマップを確認できます。...}コンフィグレーションマップにログのレベルや出力先が追加されました。moduleはロギングライブラリtimbreを利用するため,追加されたキーワードにはtimbreが含まれています。最後にgoを呼び出すことで,展開後のコンフィグレーションマップに従い初期化されたWebサーバが起動します。3000}:initiatedDuctのmoduleのつくりかたここまでで,外から見たmoduleの振る舞いを確認しました。pubsubを非同期pullで購読するためのmoduleの実装duct.module/messageを作り,moduleの振る舞いの実装方法を紹介します。ガイドにあるjavaコードです。プロジェクトIDとサブスクリプションIDで指定したキューからのメッセージの受け取り処理を開始するプログラムです。このJavaコードは,Clojureで次のように書き直せます。subscriberが受信したメッセージをreceiverに渡しています。moduleが担う処理はドメインを問わず行うべき処理なので,s-nameを文字列から作る処理をmodule化することを目指しましょう。leiningenプロジェクトをDuctのmoduleにするには,srcに以下のようなマップが書かれたファイルduct_hierarchy.ednを作る必要があります。これにより,クラスの継承と同じ考え方でコンフィグレーションマップの抽象度を上げることができます。上のコードはduct.module/messageがduct/moduleの子キーワードであることを宣言しています。stepでは,duct/moduleを継承するキーワードのマルチメソッドが呼ばれます。このマルチメソッドは次のように:fnをキーとするマップを返す必要があります。:duct/logger)}}))}):fnの設定すべき値は,コンフィグレーションマップを受取り新しいコンフィグレーションマップを返す関数です。stepでのコンフィグレーションマップの書き換えは,:fnの関数適用の結果です。上のコードは,ユーザが定義したコンフィグレーションマップに:duct.message/pubsubをキーとするエントリを追加しています。merge-configsは引数に渡されたマップをマージし,マージ後のマップを返します。moduleには,このキーワードのマルチメソッドも含める必要があります。ユーザにはduct.message/pubsubの値にIDを書いてもらうようにします。これでmoduleの出来上がりです。最後に,作ったmoduleを実際に使ってみます。キューのIDとmoduleのキーワードを含んだコンフィグレーションマップを作ります。:duct.message/pubsub}マルチメソッドでは,コールバックと購読の開始処理を書いています。s-nameには,moduleにあるSubscriptionName/createの返り値が渡ります。Covered本稿の前半でモジュールと表現しているIntengrantのマルチメソッドとは異なるものなので,英語表記にして両者を区別します。11:52はじめてのDuctTweet関連記事2019-10-07Ringアプリケーションで例外をいい感じにハンドリングする方法(Ductでの解説も含む)こんにちは!こんにちは!SPEEDA開発チームのあやぴーです。»プロフィール『経済情報で、世界を変える』をミッションに、企業・業界情報プラットフォーム「SPEEDA」やソーシャル経済ニュース「NewsPicks」、ABMを強力にサポートする「FORCAS」、スタートアップの情報収集を圧倒的効率化「entrepedia」などを提供する株式会社ユーザベースの技術チームブログです。多国通貨」をハンズオンしたら快適で楽しかったGo言語でオブジェクト指向プログラミングの基本(型編)月別アーカイブ検索@Uzabase_Techさんのツイートスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。