オニオンアーキテクチャ
ドメインロジックを中心に据え、外側の層が内側に依存する同心円状のアーキテクチャ
オニオンアーキテクチャとは
オニオンアーキテクチャ (Onion Architecture) は、Jeffrey Palermo が 2008 年 7 月のブログ記事で提唱したアーキテクチャで、ドメインロジックを中心に据え、外側の層が内側の層に依存する同心円状の構造である。原典が掲げる基本ルールは 1 つだけで、コードは自分より中心側の層には依存してよいが、中心から見て外側にある層には依存できない。結合はすべて中心に向かう、という言い方をする。ここから「DB は中心ではなく外部の一部である」という主張が導かれる。
層構造
オニオンアーキテクチャは同心円状の層で構成される。最も内側にドメインモデル (エンティティ、値オブジェクト) を配置し、その外側にドメインサービス、アプリケーションサービス、最外層にインフラストラクチャ (DB、外部 API) を置く。依存の方向は常に外側から内側に向かい、内側の層は外側の層を知らない。層の数は固定ではなく、原典もアプリケーションコアの層数は場合により変わると述べている。下の 4 層は最小構成に近い一例と考えるとよい。
┌─────────────────────────────────┐
│ Infrastructure │
│ ┌─────────────────────────────┐ │
│ │ Application Services │ │
│ │ ┌─────────────────────────┐ │ │
│ │ │ Domain Services │ │ │
│ │ │ ┌─────────────────────┐ │ │ │
│ │ │ │ Domain Model │ │ │ │
│ │ │ └─────────────────────┘ │ │ │
│ │ └─────────────────────────┘ │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘
| 層 | 責務 | 例 |
|---|---|---|
| Domain Model | エンティティ、値オブジェクト | User, Order, Money |
| Domain Services | ドメインロジック | OrderService |
| Application Services | ユースケースの調整 | CreateOrderUseCase |
| Infrastructure | 外部依存 | DynamoDB, API, メール送信 |
依存の方向
層をまたぐ依存は一方向に限られる。ここで見落とされやすいのは、依存できる相手が「1 つ内側の層」に限らない点である。自分より内側であればどの層に依存してもよい。
Infrastructure ──→ Application Services ──→ Domain Services ──→ Domain Model
Infrastructure ───────────────────────────────────────────────→ Domain Model
逆向きの依存は 1 本も許されない。Domain Model は DynamoDB の存在を名前としても知らず、内側で宣言されたインターフェースを通じてデータにアクセスする。この 1 本が入った時点で、同心円は名前だけのものになる。
TypeScript での実装
実装で効いてくるのは、リポジトリのインターフェースを内側 (アプリケーションコア) に置き、その実装だけを最外層に出す点である。原典もリポジトリについて、インターフェースはアプリケーションコアに置くが保存処理そのものは外に出すと述べている。この配置ができていると、ユースケースのテストはインターフェースの差し替えだけで DB 抜きに書ける。
// Domain Model (最内層)
class Order {
constructor(readonly id: string, private items: OrderItem[], private status: OrderStatus) {}
get total() { return this.items.reduce((s, i) => s + i.price, 0); }
cancel() { if (this.status !== 'pending') throw new Error('Cannot cancel'); this.status = 'cancelled'; }
}
// Domain Service: リポジトリのインターフェース (内側で定義)
interface OrderRepository {
findById(id: string): Promise<Order | null>;
save(order: Order): Promise<void>;
}
// Application Service: ユースケース
class CancelOrderUseCase {
constructor(private orderRepo: OrderRepository) {}
async execute(orderId: string) {
const order = await this.orderRepo.findById(orderId);
if (!order) throw new Error('Order not found');
order.cancel();
await this.orderRepo.save(order);
}
}
// Infrastructure (最外層): DynamoDB 実装
class DynamoDBOrderRepository implements OrderRepository {
async findById(id: string) { /* DynamoDB から取得 */ }
async save(order: Order) { /* DynamoDB に保存 */ }
}
クリーンアーキテクチャとの違い
狙いはほぼ同じだが、クリーンアーキテクチャがオニオンアーキテクチャの拡張版として作られた、という理解は系譜として正しくない。Robert C. Martin が 2012 年 8 月の記事でクリーンアーキテクチャを示したとき、彼はヘキサゴナルアーキテクチャ、オニオンアーキテクチャ、スクリーミングアーキテクチャ、DCI、BCE の 5 つを「細部は違うがどれも層による関心の分離を目的にしている」として並べ、その共通項を 1 枚の同心円に束ねている。オニオンアーキテクチャはその材料の 1 つという位置にある。
実務で差が出るのは踏み込む深さである。オニオンアーキテクチャの原典は円と依存の向き、そしてリポジトリインターフェースの置き場所までを示す。クリーンアーキテクチャは境界をまたぐときの作法まで規定しており、制御の流れは外から内へ進むのに依存は内向きという食い違いを依存性逆転で解くこと、境界を越えて渡すのは単純なデータ構造に限りエンティティや DB の行をそのまま渡さないことを明示している。層を切ったあとに毎回迷うのはこの受け渡しの部分なので、実装の指針としてはクリーンアーキテクチャ側の記述が使える。
Lambda での現実的な判断
原典は適用範囲についても踏み込んでおり、小規模なサイトには向かず、長く使われる業務アプリケーションや振る舞いが複雑なアプリケーションに向くと明言している。Lambda で判断が分かれるのも同じ線の上にある。
| ケース | 推奨 |
|---|---|
| 複雑なドメインロジック | オニオンアーキテクチャ |
| 単純な CRUD | 不要 (直接 DynamoDB を呼ぶ) |
導入して最も多い失敗は、層を切ったのに中心が空になることである。エンティティが getter と setter の集まりに退化し、金額の計算や取り消せるかの判定がすべてアプリケーションサービス側へ流れ出すと、中心にドメインモデルを置いた意味は消え、ディレクトリが増えただけの CRUD に戻る。中心へ置くべきものは型ではなく、その型が守る規則である。上のコード例で cancel を Order 自身に持たせているのは、この一点のためである。
理論と実装の両面から学ぶなら関連書籍が参考になる。
この記事は役に立ちましたか?
関連用語
クリーンアーキテクチャ
ビジネスロジックを外部の技術的詳細から分離し、依存関係を内側に向けることで変更に強い設計を実現するアーキテクチャ原則
ポートとアダプター
アプリケーションのコアロジックを外部技術から分離し、ポート (インターフェース) とアダプター (実装) で接続するアーキテクチャ
依存性逆転の原則
SOLID の D - 上位モジュールは下位モジュールに依存せず、両者とも抽象に依存すべきという設計原則
ヘキサゴナルアーキテクチャ
ポートとアダプターでビジネスロジックを外部依存から分離する設計パターン
YAGNI
You Aren't Gonna Need It - 今必要でない機能を先回りして実装しない原則
C4 Model
ソフトウェアアーキテクチャを 4 つの抽象レベルで図示するモデル
関連する記事
体系的に学ぶ 安全な Web アプリケーションの作り方 第 2 版は初版と何が違うか - 買い直し判断ガイド
通称「徳丸本」こと体系的に学ぶ 安全な Web アプリケーションの作り方 第 2 版 (2018 年) と初版 (2011 年) の違いを出版社公表の改訂内容から整理。章の削除 / 新設 / 追加点の一覧と、初版所有者が買い直すべきかの判断基準を解説します。
設計の引き出しは経験だけでは増えない
実務経験だけでは設計の引き出しに限界があります。なぜ経験だけでは不十分なのか、本が設計力に対して果たす、他で代えにくい役割を論じます。
技術書の読む順番戦略 - 複数冊を組み合わせて理解を加速させる
技術書を 1 冊ずつ読むのではなく、複数冊を戦略的に組み合わせることで、1 冊では届かない理解の深さと広さに達する方法を解説します。