- Ruby / Rails関連
READ MORE
原著者の許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。画像はすべて元記事からの引用です。
(前編の続き)
弊社では2019年初頭からエンジン分離copを使い続けて成功を収めています。今やコードベースには40個のエンジンができあがり、Rubyコードの35%を締めています(cloc app
とcloc engines/**/app
の比較)。このcopは、既存コードのリファクタリングにも、新規プロジェクトの開始時にも有用であることが実証されています。
訳注: AlDanial/cloc:はさまざまなプログラミング言語を対象にソースコードの空行やコメント行や実際の行数をカウントするツールです。
弊社のドメインモデルでは、Martin Fowlerの「Bounded Context」に基づいてインクリメンタルなコード切り離しを進めるときに以下の手順に従っています。
_legacy_dependents.rb
に登録するこれらのcopがまっさらぴんのエンジン作成にも有用であることがわかってきました。2つのcopを最初から有効にしておけば、新しいエンジンのデータがモジュール化されてシステムの他の部分から切り離されます。このおかげでエンジンをモノリスで手軽に立ち上げられるようになり、(必要なら)後でネットワーク越しでアクセスする通常のサービスに切り出すのも楽にやれます。
他のツールと同様、弊社のcopにも強みと制約があります。
弊社では、これら新しいcop以外にもモノリスでモジュール化を強制する方法をいろいろ調べてきました。
弊社エンジニアのKevin Millerは、Railsのモデルを以下のように拡張することを社内レベルで提案しています。
.api_association
: これは既存の.readonly
と同じだが、User.all.api_association.last.company.readonly? == true
のような関連付けチェインのみの形への強制も行う。これにより、あらゆる書き込みをサービスAPI経由のみでしか行えないように強制できそう。
.with_whitelisted_methods
: 背後にあるモデルで、ホワイトリストに記載されていないメソッドをすべてエラーにし、背後の残念なモデルにかかわらずに特定のメソッドやカラムだけを公開できるようにする。
スタートアップ企業Root社の良記事「The Modular Monolith: Rails Architecture」では、エンジン同士のモジュール分離を強制する方法について考察しています。テスト時に特定のエンジンだけをいくつか読み込むという方法です。
エンジンA、B、Cがあるとしよう。AはBに依存し、BはCに依存する。Cのテストを走らせるときは、エンジンCだけを読み込み、AやBは読み込まない。Bのテストを走らせるときは(BがCに依存するので)Cは読み込むがAは読み込まない。Aのテストを走らせるときはBやCを読み込む。
同記事より大意
この手法はまっさらエンジンではうまくいきそうですが、既存のコードベースをインクリメンタルにモジュール化するときはそれほどでもなさそうです。
save
にフックをかけるApplicationRecord
クラスにフックをかけ、そのモデルが定義されているエンジンの外からsave
やsave!
を呼び出そうとしたときにブロックするという手法です。
Active Recordの関連付けローダーにフックをかけて、エンジンを越境する関連付けの読み込みをブロックするという手法です。これは、あらゆる関連付けをcopで削除するという上述の手法と同等のように思われます。
エンジンを別アプリのインスタンスにデプロイして、やりとりをネットワーク越しに限定します。これは、ある意味で弊社で現在追求している手法と同じです。
aftersave
フックとメタプロを併用して、エンジンごと(またはモデルごと)にバックログ的なものを作成し、production環境でのエンジン内save
とエンジンの外からのsave
やコミットの割合を表示します。値が十分小さくなったら、Sentryのwarningとフルスタックトレースを併用します。
ここで弊社のオープンソース哲学と、本記事でご紹介したcopたちのステータスについて簡単に述べておきます。
Frexportは、コミュニティに支えられている既存のリポジトリを利用可能なら、そこに置かせてもらうようにしています。しかし既存のリポジトリが要件に合わないこともあるので、その場合は弊社のコードを自社リポジトリに置く方が理にかなうと考えます。
RuboCopチームとの議論によれば、Railsエンジン分離copたちは後者に該当します。そこで弊社はそれらのcopを含む自社製copのリポジトリを新たに作成しましたが、他のupstream先は設けませんでした。
Railsエンジンは、弊社の複雑なモノリスの管理に役立つツールであることが実証されています。会社が成長し続けるにつれて、弊社の関心はバックエンドサービスをネットワークで分離して、GoogleのProtocol Bufferで定義された、より強固な「インターフェイスファーストAPI」を用いることに移りつつあります。弊社は今後も、コードの一部を新サービスに切り出す移行パスとしてRailsエンジンを使い続けるつもりです。そして弊社はRailsエンジンを移行中にも、新しいサービス内部のモジュールにも長期的に用いていくことを期待しています。
この方面についての皆さまの経験談についても知りたいと思います。ご意見などございましたらぜひ弊社までお知らせください。