TL;DR

  • 従来のレイヤードアーキテクチャは変更に弱く、保守性がすぐに下がる。正しく扱えば堅牢にもなるが、強制する仕組みがないため、追加のルールや開発者の自制心に頼ることになる。
  • 保守性の低下は、データベース中心の設計、近道の実装、テストの書きづらさ、ユースケースの見つけづらさ、並行作業の難しさという 5 つの問題から生じる。

内容

従来のレイヤードアーキテクチャ

単純なレイヤードアーキテクチャは、Web 層、ドメイン層、永続化層からなる。Web 層は HTTP リクエストを受け取り、ドメイン層のサービスに渡す。ドメイン層はビジネスロジックを実行し、必要に応じて永続化層のコンポーネントを呼び出して、データストアにあるエンティティを取得・変更する。

適切に扱えば堅牢なアーキテクチャになるが、変更に弱く、保守性がすぐに下がる。不適切な依存関係があっても受け入れてしまい、保守性の維持は開発者の自制心と勤勉さに完全に委ねられる。保守性が下がる原因は、次の 5 つの問題である。

データベース中心の設計になってしまう

レイヤードアーキテクチャでは、Web 層がドメイン層に依存し、ドメイン層が永続化層(データベース)に依存する。依存の向きに沿って開発すると、すべてが永続化層の上に組み立てられ、データベースを中心に開発が進みがちになる(Domain Modeling Made Functional 第2章 Understanding the Domain)。本来は、ビジネスロジック(振る舞い)を正しく実装してから、永続化層や Web 層を実装するべきである。

ORM の利用もデータベース中心の設計を助長する。ORM を使うなということではない。ORM が管理するエンティティは通常、永続化層に置かれる。レイヤードアーキテクチャには「同じ層か下の層のコンポーネントにしかアクセスできない」という制約しかないので、ドメイン層はこのエンティティにアクセスできる。その結果、ドメイン層の至るところでエンティティが使われ、ドメイン層と永続化層が密結合になる。

密結合になると、ドメイン層はエンティティをビジネスモデルとして扱い、ビジネスロジックに加えて、Eager load か Lazy load か、トランザクションといった永続化層の関心事まで扱うことになる。これは単一責任の原則に反する。結果として、ドメイン層に影響を与えずに永続化層を変更するのが難しくなる。

近道の実装が入り込む

上の層のコンポーネントにアクセスする必要が出たとき、レイヤードアーキテクチャでは「そのコンポーネントを自分の層に下ろす」という近道が取れてしまう。一度でもこうした近道をすると、ほかの場所でも同じことが起きる(割れ窓理論)。これを何年も繰り返すと、最下層の永続化層が肥大化する。

近道の実装を持ち込ませたくないなら、レイヤードアーキテクチャは選択肢から外れる。どうしても採用するなら、ルールに従わないときにビルドを失敗させるなど、強制する仕組みが必要である。シニアエンジニアのレビューでは不十分である。

テストが書きづらい

時間が経つと、途中の層を飛び越えてアクセスする実装も出てくる。これには 2 つの悪影響がある。1 つは、ビジネスロジックが Web 層にも書かれ、ドメイン層に収めるべきロジックが散らばることである。もう 1 つは、Web 層をテストするときに、ドメイン層だけでなく永続化層の依存も制御する必要が出ることである。モックする対象が増えるとテストが複雑になり、最終的にテストが書かれなくなる。依存関係を把握するのも難しくなる。

ユースケースが見つけづらい

アーキテクチャで重要なのは、機能を追加・変更する場所がコードベースのどこかを簡単に把握できる構造であることだ。レイヤードアーキテクチャはこれが苦手である。ビジネスロジックがいろいろな層に散らばりやすいうえ、1 つのサービスが扱うユースケースの数にルールがない。そのため、時間とともに複数のユースケースを抱える肥大化したサービスが生まれる。

たとえばユーザ登録のユースケースを探すとき、RegisterUserService のように責務に特化したサービスならすぐ見つかる。しかし、ユーザに関するすべてのユースケースを扱う UserService では、クラスの中から該当するコードを探す手間がかかる。こうしたサービスは Web 層の多くのコンポーネントから依存されるので、テストもしづらい。

並行して作業しにくい

『人月の神話』には「遅れているソフトウェアプロジェクトへの要員追加は、プロジェクトを更に遅らせるだけである」という記述がある。それでも、規模が健全であれば、開発者を増やして開発速度を上げることも期待できる。ただし、そのためにはアーキテクチャが並行作業を支えている必要がある。

レイヤードアーキテクチャでは、すべてが永続化層の上に作られるので、永続化層、ドメイン層、Web 層の順に実装する必要がある。そのため、1 つの機能を同時に作業できる開発者は 1 人だけになる。永続化のロジックがビジネスロジックに混ざっているので、先にインタフェースを定義してそれに対してプログラミングする戦略も取れない。サービスが肥大化していると、異なる機能を別の開発者が同時に開発するのも難しい。同じサービスを変更してマージコンフリクトが起きたり、リグレッション(デグレード)が起きたりする。

キーワード

自分の理解

本書では「多層アーキテクチャ」(Multi-tier)と訳されている。しかし、ここでの層は論理的な層を指しているので、「レイヤードアーキテクチャ」(Layered)のほうが適切だと判断し、読み替えて読み進めた(Layer と Tier の違い)。

似た話が Domain Modeling Made Functional 第3章 境界づけられたコンテキストの中のコード構造 にもある。