Shopifyはいかにしてモジュラモノリスへ移行したか

 
ShopifyのシニアエンジニアであるKirsten Westeinde氏がShopify Unite 2019で、Shopifyにおけるモジュラモノリス(modular monolith)への展開について論じた。変更をいつ行うか、どのように達成するか、といった判断にデザインペイオフラインを使用したこと、ターゲットアーキテクチャからマイクロサービスを除外した理由、などがその内容だ。
重要な点は、モノリスは必ずしも悪いアーキテクチャではなく、単一のテストおよび展開パイプラインなど多くのメリットがある、ということだ。新たな機能を短期間で提供する必要のあるプロジェクトを立ち上げるには、これは非常に有用だ。アーキテクチャの改善に着手すべきなのは、"設計のペイオフライン"を越えた時、すなわち設計の悪さが機能開発を妨げるポイントにおいてのみである。Shopifyの場合、アーキテクチャの改善はマイクロサービスへの移行ではなく、モジュラモノリスへの移行という形になった。この判断によって、単一のテストと展開パイプラインというモノリスのメリットと、コードのモジュール化や分離といったマイクロサービスのメリットを両立することができたのだ。
Westeinde氏は、モノリシックアーキテクチャはプロジェクトの出発点として適当だという考えから、"新しいプロダクトや新たな企業はモノリスから始めることを、実際に推奨していました"、と述べている。そのメリットとして氏は、次のようなものを挙げる。
  • すべてのコードがひとつのプロジェクトに含まれる。
  • コードベースが単一であるため、テストやデプロイが容易である。
  • すべてのデータが利用可能であり、サービス間で送信する必要はない。
  • インフラストラクチャセットが単一である。
このようなメリットを活かしたShopifyは、最初は小さなRuby on Railsモノリスとして始まり、やがて非常に大きなコードベースに進化した。拡大に伴ってShopifyは保守不能になり始めたため、新しい機能を提供することが難しくなった。例えば、ひとつのコードの変更によって、一見無関係なコードに意図しない副作用が発生したり、アプリケーションのビルドとテストに長時間を要したりするようになったのだ。
Westeinde氏は、Martin Fowler氏の提唱する"設計=スタミナ仮説(design stamina hypothesis)"を引用して、アーキテクチャをリファクタリングすべき時が来た、と説明した。悪い設計によって機能開発が妨げられるようになれば、それは設計のペイオフラインを越えたということであり、これを修正するためのリソース投資が合理的なものになる。
Shopifyは当初、保守性のよい代替アーキテクチャとしてマイクロサービスを検討していたが、分散システムの複雑性を理由に除外され、代わりに、より保守性のよいモノリスが求められることになった。
私たちがモノリスの望ましい部分として考えていたことのすべては、コードの所在とデプロイ先がひとつの場所であることの結果である、ということが分かりました。同時に、私たちが問題だと考えていたことのすべては、コード内の異なる機能間にバウンダリが欠けていたことの直接的な結果である、ということが分かったのです。
結果として、モノリスのような単一で展開可能なユニットを維持しながら、マイクロサービスのようにシステムのモジュール性を高めることが設計目標であるという認識に達したのだ、とWesteinde氏は説明する。これを実現するため、Shopifyでは、モジュラモノリス(modular monolith)パターンを採用した。この方法では、コード間のバウンダリは可能になるが、コードは同じ場所に配置され、同じ場所に展開される。マイグレーションパスは次のようなものだ。
  • コードの再編成: 当初のコードは、典型的なRailsアプリケーションのような編成で、トップレベルのパーツには"controllers"など、技術的コンポーネントにちなんだ名前が付けられていた。これを"billing"や"oders"など、ビジネス機能に基づいた編成に変えることで、コードの所在を見つけやすくした。
  • 依存関係の分離: 各ビジネスコンポーネントを相互に分離し、公開APIを通じて使用できるようにした。各コンポーネントの分離の達成状況を追跡するために、Wedgeという名前の社内ツールが開発された。このツールはコールグラフを作成して、コンポーネント間のコールなど、違反しているコールを見つけ出すことでこれを行う。
  • 境界の施行: 各コンポーネントが100%分離されると、それらの間には境界が施行される。基本的な考え方は、明示的に依存していないコンポーネントのコードへのアクセスに対して、ランタイムエラーを発生させる、というものだ。このような依存関係を宣言することにより、依存関係グラフで依存関係を視覚化することも可能になる。
Westeinde氏は結論として、これがビジネスニーズに基づいてアーキテクチャを進化させる方法の成功例である、説明している。
優れたソフトウェアアーキテクチャというのは、常に進化するタスクです。アプリの正しいソリューションは、運用する規模によってまったく違います。
講演の全内容はオンラインで見ることができる。対応するブログ記事も公開されている。