
はじめに
こんにちは。弁護士ドットコムで開発をしている田所といます。 私は今年の 3 月に弁護士ドットコムへ入社し、日本最大級の弁護士/法律ポータルサイト「弁護士ドットコム」の開発に携わっています。主な役割は、レガシーシステムの改善とアジャイル開発の推進です。 本稿では、入社してから半年間で取り組んできた、レガシーシステムの改善の取り組みについて紹介します。
- はじめに
- 「本体サイト」が抱えている課題
- modulesディレクトリについて
- 境界づけられたコンテキストをベースにしたモジュラモノリスアーキテクチャの導入
- 新しいモジュールに漸進的に移行する
- 組織全体で改善していく
- まとめ
「本体サイト」が抱えている課題
弁護士ドットコムの社内で「本体サイト」と呼ばれている bengo4.com は、Git のコミットをたどれる範囲で、開発開始から約 15 年半が経過している、歴史のあるシステムです。
多くのページは、PHP によるサーバーサイドレンダリングで表示されており、一部のページや機能のみ、PHP による REST API と React の組み合わせで実装されています。
PHP の処理には、Yii という Web アプリケーションフレームワークを採用しています。
このような歴史あるリポジトリの多くがそうであるように「本体サイト」も典型的なレガシーコードらしい課題を多く抱えています。その中でも中心的な課題と言えるのが、modules ディレクトリのスパゲティ状態のコードです。
modulesディレクトリについて
modules ディレクトリは、Yii にもともと備わっているモジュール機能を利用するためのディレクトリです。Yii のモジュールは、内部に MVC のすべてを内包するいわば「縦切りのミニ Web アプリケーション」です。たとえば、EC サイトの販売ページと管理画面のような、1 つのサービスを構成する複数アプリを分割して配置する使い方や、複数のメディアサイトを 1 台のレンタルサーバーでホスティングするといった使い方を想定した機能です。
リクエストが Yii のアプリケーションに到達すると、Router がモジュール内の Controller に処理を振り分けます。Controller は Model を使って何らかのロジックを実行し、View に処理結果を渡してレスポンスを構築し、ブラウザーに返却します。

bengo4.com では、この modules ディレクトリを、プロジェクト単位でソースコードを配置する場所として長らく使用してきました。新しい機能追加プロジェクトが立ち上がると modules 配下にその機能名やプロジェクト名のディレクトリが作られ、コードがそこに貯まっていく、という運用です。
このやり方は、施策を撤退するときに関連するコードを一挙に削除しやすいというメリットがある一方で、モジュール内に MVC 全てを含むことに由来する根本的な問題を抱えていました。
モジュール内に MVC すべてを含んでいることによる問題
bengo4.com はコンテンツサイトなので、View にはユーザーニーズに応えるためのさまざまな要素が表示されます。例えば、 bengo4.com に「弁護士プロフィール」というページがあります。法律相談をしたいユーザーに向けて、弁護士が自身のプロフィールを公開しているページです。 このページは「弁護士プロフィール」モジュールに属していますが、このページ内には「みんなの法律相談」という別のモジュールを使わないと取得できない、過去の法律相談への回答一覧を表示するエリアがあります。 このように、プロジェクトや機能単位でモジュールを分割し、モジュール内に View 層の実装が含まれると、View の要請によってモジュールをまたいだ依存がどうしても発生してしまいます。
ActiveRecord をデータの受け渡しにそのまま利用していることによる問題
ディレクトリの分割方針に由来する問題とは別に、modules ディレクトリにはもう一つ問題がありました。それが ActiveRecord をデータの受け渡しに利用していることです。
Yii の Model 層では ActiveRecord パターンが利用されています。モジュール内で層をまたいでデータを受け渡す際、多くの場合 ActiveRecord インスタンスがそのまま使われていました。これは Yii のモジュール機能による制約ではなく、過去の実装方針が慣例として引き継がれてきた結果です。
プロダクト開発の初期段階では、開発速度を優先して ActiveRecord 中心に Model と View を組み立てる構造はよく見られます。しかし、プロダクトが成長してコード量が増え、上述の「モジュールをまたいだ依存」と組み合わさった結果、modules ディレクトリ全体が、ActiveRecord を中心とした大きな団子状態になっていました。
さらに、多くのクラスが ActiveRecord を経由して Yii に依存しているため、アプリ全体がフレームワークにロックインされた状態です。 また、ドメインロジックを集約するクラスが少なく、その結果 Controller や View にロジックが散在する要因にもなっていました。
まとめると、modules ディレクトリは以下の図のように、Controller が他のモジュールの ActiveRecord や View に依存しており、また View が直接 ActiveRecord に依存している結果、モジュール内の全ての層が Yii に依存している状態でした。

境界づけられたコンテキストをベースにしたモジュラモノリスアーキテクチャの導入
上記の課題を解決するために、新たなモジュール構造を導入することにしました。以降、この文章では modules ディレクトリ配下にあるモジュール群を「旧モジュール」、本取り組みで新たに作成するモジュール群を「新モジュール」と呼びます。
新モジュールは、世の中でドメイン駆動設計(DDD)としてまとめられている以下のような知見に基づいて設計しました。
- モジュラモノリスアーキテクチャを採用する
- モジュールはプロジェクト単位ではなく「境界づけられたコンテキスト」を意識して分割する
- モジュール内はオニオンアーキテクチャを採用し、ユースケース層、ドメイン層、インフラ層のみをモジュールに含む。プレゼンテーション層はモジュールに含まない
- CQRS パターンを利用し、更新系ユースケースと参照系ユースケースで利用するドメイン層・インフラ層を分ける
最終的なアーキテクチャの構成はこのようになりました。

従来の modules ディレクトリと別に、新たに contracts と implementations という2つのディレクトリを作成しました。contracts ディレクトリには新モジュールのユースケースが外部に向けて公開するインターフェース定義を、implementations ディレクトリには新モジュールのロジックの実装を配置します。 この構造により、DIのバインディング定義以外では、implementations ディレクトリが外部から参照されることはありません。
また、新モジュール内には Controller や View といったプレゼンテーション層を含まないため、View の都合によってモジュールをまたいだ依存が発生することもありません。
アーキテクチャの主な設計方針について、以下で説明します。
モジュラモノリスアーキテクチャの採用
新モジュールのもっとも基本的なアイデアは、モジュラモノリスアーキテクチャです。このアイデアによって、今回の取り組みが可能になったと言っても過言ではありません。モジュラモノリスには、今回の取り組みにマッチするいくつかの利点があります。
- マイクロサービス分割には DB の分割が必要になります。DB を共有するマイクロサービスは「分散モノリス」というアンチパターンに陥る可能性が高くなります。しかし DB の分割は大掛かりなプロジェクトになりがちです。モジュラモノリスは、単一 DB を共有しながら、ソフトウェアの論理構造だけを分割することを許容します。
- モジュラモノリスは、インフラの分割を伴わず、モジュール間でプロセスも共有しています。このため、モジュール境界を間違えたことに後から気付いたとしても、修正が比較的容易です。モジュール境界に自信が持てない場合でも、漸進的にリアーキテクチャを進めることができます。
- 今回の取り組みは、日常の開発業務の中で少しずつ進めていくことを想定しています。そのため、取り組みの完了がいつになるかを見積もることが困難です。モジュラモノリスは、追加のインフラが不要なため、インフラコストの増大を気にすることなく、ある程度自由なペースで取り組みを進めることができます。
モジュール境界の設計
モジュール境界は「境界づけられたコンテキスト」という考え方を参考に設計しました。書籍「ドメイン駆動設計をはじめよう」でも『業務領域は「発見」であり、区切られた文脈は「設計」です』(P.49)と書かれており、モジュールの分割は、それによって開発チームが迷わずに安定して開発を進められることが目的であるべきです。 境界づけられたコンテキストは「既存のチーム構成」と「ユーザーの役割」を手掛かりに考えました。長年安定しているチーム構成は、コンテキスト境界と一致する可能性が高いですし、ユーザーの役割が変化したり、異なるユーザーが登場するポイントも同様だからです。
オニオンアーキテクチャの採用
モジュール内の構造はオニオンアーキテクチャを採用しました。DDD の実装面で利用される階層型アーキテクチャは、どれも似たような構造をしていますが、個人的に ログラスの松岡さん が書かれた「新卒にも伝わるドメイン駆動設計のアーキテクチャ説明(オニオンアーキテクチャ)[DDD]」という記事の図がわかりやすく、メンバーに説明しやすかったので、採用させていただきました。

新モジュールには、ドメイン層、インフラ層、ユースケース層だけを実装し、プレゼンテーション層は旧モジュール内に残すようにしています。これにより、新モジュールはプレゼンテーション層の都合に左右されず、ドメイン層の正しさを中心に据えて開発できます。
CQRSパターンの採用
bengo4.com はコンテンツサイトなので、更新に比較して参照が圧倒的に多く、また参照のパターンが複雑です。ここは業務 SaaS などの開発と大きく異なる部分です。CQRS は、ドメイン層とインフラ層のコードを、更新系と参照系で完全に分けて実装します。これにより、ドメイン層の各クラスの責務をシンプルに保つことができます。 インフラ層は、更新系ではリポジトリクラス、参照系ではクエリクラスを利用します。リポジトリクラスは、Yii が提供する ActiveRecord を利用しても良いですが、クエリクラスは PHP 標準の PDO を利用するルールにしています。当然リポジトリクラスの I/O はドメインモデルを利用し、ActiveRecord への依存はリポジトリ内に閉じられています。この方針により、Yii への依存を最小限にし、将来のフレームワークの乗り換えに備えています。
新しいモジュールに漸進的に移行する
今回の取り組みは、日常の開発業務の一環として、漸進的に進めていく方針としています。具体的には以下のような手順を考えています。
- サイトに新機能を追加するにあたり、新たなユースケースが必要になる
- ユースケースを新モジュールに実装し、そのユースケースの実現のために必要なドメイン層やインフラ層を実装する
- 新たに作ったユースケースを使いまわせる既存機能が存在していれば、リファクタリングし、新モジュールのユースケースを使った処理に書き換える
- 不要になった旧モジュールのロジックを削除する
またこの取り組みと並行して、フロントエンドの React 化を進めているメンバーもいるため、そのプロジェクトとも連携しています。具体的には以下のような手順になります。
- フロントエンドの React 化に伴い、バックエンドに新たな API が必要になる
- API を実現するユースケースを新モジュールに実装し、そのユースケースの実現のために必要なドメイン層やインフラ層を実装する
- フロントエンドを React 版に切り替えたら、PHP でサーバーサイドレンダリングしていた旧モジュール内の関連コードを削除する
この 2 パターンを軸に取り組みを進め、徐々に旧モジュール内のコピペコードや Yii に依存したコードを減らしていきます。最終的には、旧モジュール内のコードが十分少なくなった段階で、Yii を捨てる想定です。
組織全体で改善していく
この取り組みは規模が大きく、私一人では進められません。bengo4.com の開発メンバー全員で協力して取り組む必要があります。しかし、新しいアーキテクチャの習得は誰もがすぐにできるものではありません。そのため、組織全体で取り組みが進められるように、いくつかの仕掛けを用意しました。
AIエージェント用のドキュメント整備
今後ますます重要になる AI エージェントを活用し、開発生産性を高めるために、AI が参照できるドキュメントを整備しています。
具体的には、実装サンプルとなるダミーモジュールを作り、アーキテクチャの各構成要素の実装方法、依存関係のルール、命名ルールなどをコードコメントとして記述しました。
その上で、機能を実装する際に、どのような順番でどのようなクラスを実装すべきかを説明し、実装ステップに到達したら参考にすべきサンプル実装を参照するよう指示しています。 現時点でもそれなりに機能していますが、今後ノウハウをためながらさらに洗練させていく予定です。
CIによるガードレール
deptrac という、レイヤー間の依存の方向をチェックするツールを導入しました。これにより、アーキテクチャから逸脱したクラスを書くと、CI パイプラインで検出されます。開発メンバーの習熟度にばらつきがあったとしても、最低限アーキテクチャが劣化しない状態を保てます。
また新モジュール内は PhpStan のレベルを最高に設定しています。PhpStan とは PHP の静的解析ツールで、最高レベルで運用すると、静的型付け言語に近い型安全性を手に入れることができます。
出張ペアプロ
最後はめちゃくちゃ人力ですが、私自身が所属チーム以外のメンバーともペアプロを行い、新しいアーキテクチャに慣れてもらう取り組みもしています。まだペアプロできている人数は多くありませんが、取り組みの広がりに合わせてより多くのメンバーとペアプロの機会を作っていくつもりです。
まとめ
今回紹介した取り組みは、一度にすべてを置き換えるのではなく、日々の開発の中で少しずつ進めることを前提にしています。 モジュラモノリス、境界づけられたコンテキスト、オニオンアーキテクチャといった考え方を組み合わせることで、レガシーシステムのリアーキテクチャを無理なく推進できるよう工夫してみました。 同じようにレガシーシステムの改善に取り組んでいる方の参考になればうれしいです。