「モジュラモノリス」と言ってもいくつかのパターンがあるのではないかと思い、整理してみたい。 なお、ここではRuby on Rails(MVCアーキテクチャ)とpackwerkを用いたアプリケーションを想定している。なのでこの文章で使う「パッケージ」は「モジュール」と同義と考えて良い。

ここまでの試行錯誤(ChatGPT 4oと会話しただけだが)を全部すっとばして結論から言うと 「パッケージ分割の方針」✕「パッケージ隔離の強度」 の二軸での整理を考えてみた。

まず 「パッケージ分割の方針」 はコードをどの単位でまとめるかという方針で、例えばビジネスドメイン単位で分けるとか技術的機能単位で分けるみたいな話である。

一方、「パッケージ隔離の強度」 はパッケージ間の結合をどこまで制限するかの強弱で、パッケージ間の結合を許容するから全く許容しないまでのどのあたりを落とし所にするかという話だと思ってほしい。

パッケージ分割の方針

自分が思いついたパッケージ分割の方針は下記の3つ。

1. アプリケーション分割型

  • アプリケーション層(View, Controller)単位で整理する
  • e.g. packs/api/, packs/admin/

2. ビジネスドメイン分割型

  • 事業的な関心事に沿って整理する
  • e.g. packs/orders/, packs/users/

3. マイクロモジュール型

  • 汎用的な(そしておそらくステートレスな)ロジックを独立したモジュールとして切り出す
  • e.g. packs/billing_logic/, packs/address_validation/

パッケージ隔離の強度

パッケージ隔離の強度は大きく2つのパターンに分けた。

1. ルーズパッケージング型

  • モジュールを作るが、依存関係の制約は緩い
  • packwerkの設定でいうとenforce_dependencies: false もしくはもう少し制限を強めて enforce_dependencies: true にしつつpackage_todo.ymlで違反を無視する

2. 完全分離型

  • モジュール間の暗黙的な依存の排除を目指す
  • packwerkの設定でいうとenforce_dependencies: true 且つ enforce_privacy: true にしてパッケージのpublicなAPI経由でのデータ連携をする

分割方針と隔離方針のマトリクス

これを表にすると下記のようになる。

分割の方針 \ 隔離の方針 ルーズパッケージング型(緩い隔離) 完全分離型(厳格な隔離)
アプリケーション分割型(機能ごと) 最も移行しやすい。まず機能単位で整理し、境界の厳密化は後で考える。 アプリケーション層の完全分離。UI / API / バックグラウンド処理を明確に分ける。
ビジネスドメイン分割型(ドメインごと) ドメイン単位の整理はするが、依存関係は多少緩い(段階的移行向け) 「各ドメインは完全に独立」→ ほぼマイクロサービス化。API連携が前提。
マイクロモジュール型(小さなロジック単位) どのパッケージからでも利用できる共通モジュールを作る(ユーティリティ的) 特定のモジュールが特定のパッケージ内でのみ利用できる(強いカプセル化)。

それぞれのイメージ

アプリケーション分割型

アプリケーション分割型

  • アプリケーション層とモデルの依存関係をまず把握したいのであればこれで良さそうに見える

ビジネスドメイン分割型

ビジネスドメイン分割型

  • マイクロサービス化を目指すのであればビジネスドメイン分割型✕完全分離型を目指す

マイクロモジュール型

マイクロモジュール型

  • 使いまわしたいロジックが多いのであればこの分け方もできそうだが、正直うまくフィットするケースが思いつかない

おわりに

まだまとまりきっていないので後で更新するかもしれない。重要なのはどの方針で行くのか組織内で認識合わせをしておくことだと思う。モジュラモノリスと言ってもいくつかのタイプがあるので、ここの認識が揃っていないとうまく前に進めることが難しいと思う。