概要
アーキテクチャは"進化"及び"トレードオフの歴史"である
メインフレームからクラウドネイティブ、そして AI ネイティブの現在(2026 年 1 月) までのトレンドを俯瞰してみた
どのアーキテクチャにも、メリット・デメリット がある
トレンドが最強と言う訳ではなく、プロジェクトの状況(ドメインの複雑性・プロジェクト体制・リリース方針など)に合わせて選定する事が求められているのではないか
【2026 年の重要ポイント】
AI エージェント連携・マルチエージェントシステムが主流になりつつある中、従来の Modular Monolith や Vertical Slice は依然として重要。ただし、その上に AI 層が追加される 形で進化している。
ここでは、変遷を読みやすくするために、次の 2 つの軸に分けて整理する
(A) システム形態の変遷:
- 運用・デプロイ単位(モノリス/マイクロサービス/サーバレス等)
(B) システムアーキテクチャ選定の変遷:
- コード構造・依存方向(レイヤード/DDD/クリーン等)
1. (A) システム形態の変遷
※年代は“登場年”ではなく、“普及の目安”です
| 普及し始めた年代 | 形態 | 代表例 | 主なメリット | 主なデメリット |
|---|---|---|---|---|
| ~ 1980 年 | 集中型 | メインフレーム(IBM System/370 等) | データ整合性が取りやすい/運用を中央で統制しやすい/高い信頼性 | 高コスト/変更リードタイムが長い/ベンダーロックイン/部門の小回りが利きにくい |
| 1980 年後半~ | C/S(2 層・3 層) | PC アプリ+ DB(Oracle、SQL Server) | GUI で操作性向上/ローカル処理で応答性が良い/部門単位で導入しやすい | クライアント配布・更新の負荷(バージョン不整合)/障害点が増える/水平スケールしづらい |
| 1990 年後半~ | Web(中央集約) | ブラウザ+ Web サーバ(Apache、IIS) | クライアント更新が不要/中央で統制しやすい/スケールの発想が取りやすい | アプリの肥大化(モノリス化)/リリースの心理的障壁が高い/多チームでの調整コスト増 |
| 2000 年頃~ | エンタープライズ | 大規模 Web アプリ(Java EE、.NET、Spring) | フレームワークで開発効率向上/エンタープライズ機能(トランザクション・セキュリティ)が充実/大規模開発に対応 | 単一デプロイのため変更影響が広範囲/アプリ肥大化でリリース頻度低下/部分的なスケールが困難 |
| 2010 年前半~ | マイクロサービス | 独立サービス群(REST API、Docker、Kubernetes) | サービス単位のスケール/部分改修・部分リリース/チーム境界と合わせやすい | 分散の複雑性(通信・冪等性・障害分離)/データ整合性が困難/運用負荷(監視・オンコール)増 |
| 2015 年頃~ | サーバレス | FaaS(AWS Lambda など) | サーバ管理・容量計画の負担減/イベント駆動と相性が良い/従量課金で最適化しやすい | ベンダー依存/ローカル再現の難しさ/観測性を設計しないと障害対応が困難/実行制約(タイムアウト等) |
| 2020 年頃~ | モダンなモノリス | Modular Monolith(モジュール境界を持つモノリス) | デプロイは単純/内部は機能境界で分割され保守性が高い/段階的にマイクロサービス化しやすい | 境界(依存方向・契約)を破ると再肥大化/チームが巨大だと調整が必要/"分散できない"制約も残る |
※AWS Lambda は 2014 年に登場し、普及は 2015 年以降が目安
💡 各時代の変遷理由
各時代の形態は、前の時代の課題を解決するために生まれた
- 集中型(メインフレーム)の課題(高コスト・遅い) → C/S で部門ごとに導入可能に
- C/S の課題(配布・更新) → Web で中央集約
- Web の課題(肥大化・調整コスト) → エンタープライズフレームワークで開発効率化、ただしアプリ肥大化は継続
- エンタープライズの課題(単一デプロイ・肥大化) → マイクロサービスで独立性確保
- マイクロサービスの課題(運用複雑性) → サーバレスで運用負荷削減、または Modular Monolith で分散を避けつつ保守性確保
モダンなモノリスの再評価については、詳細は 3.2 節 で触れる
2. (B) システムアーキテクチャ選定の変遷
※年代は“登場年”ではなく、“普及の目安”です
| 普及し始めた年代 | 設計スタイル | 代表例 | 主なメリット | 主なデメリット |
|---|---|---|---|---|
| 1970年代後半〜 | MVC (GUI) | Smalltalk(元祖), Swing(Javaでの普及例) | データ(M)と表示(V)の分離/同一データの複数ビュー表示/インタラクティブなUI管理 | ビューとモデルが密結合になりやすい/デスクトップアプリ以外の適用が困難 |
| 1990年代末〜 | Web MVC | Struts, Rails, Spring MVC, Django | リクエスト処理の定型化/URLと処理(Router)の紐付けが明確 | Fat Controller / Fat Model問題/ドメインロジックの置き場所が不明確になりがち |
| 1990 年頃~ | レイヤード | 3 層(Spring、Struts)/ 初期 J2EE(EJB) (※Spring/Struts は 2000 年代以降の代表例) | 構造が直感的で教育しやすい/分業しやすい/小〜中規模なら十分に有効 | 層またぎ依存で変更影響が広がる/ドメインが薄くなりがち/ビジネスルールが散らばる |
| 2000 年頃~ | SOA | SOAP/ESB(Oracle SOA Suite) | 既存資産を活かしながら連携/全社レベルの標準化・ガバナンスが効きやすい | 規格・運用が重い/ESB(中央バス)が単一障害点・ボトルネックになりやすい |
| 2005 年頃~ | 依存制御 | クリーン/ Onion/ Hexagonal (※提唱・普及時期はそれぞれ異なる) | 外部(DB/UI/API)の変更に強い/ドメインを単体テストしやすい/置換・移行がしやすい | 抽象化(インターフェース)が増える/ボイラープレート増/ルールが曖昧だと逆に複雑化 |
| 2010 年頃~ | イベント駆動 | Pub/Sub/非同期(Kafka、RabbitMQ) | 疎結合(購読者の追加が容易)/負荷分散・障害分離しやすい/リアルタイム処理に向く | 因果追跡が難しい/冪等性の設計が必須/データの最終的な整合性を許容する必要がある |
| 2010 年後半~ | Vertical Slice | Feature 単位分割(Jimmy Bogard) | 1 機能の追加/修正が 1 箇所で閉じる/認知負荷が下がる/並行開発しやすい | 横断関心事(認可・ログ等)の共通化に工夫が必要/似たコードが増える(許容が必要) |
| 2010 年頃~ | CQRS | Command/Query 分離(Greg Young) | 読み取り性能を最大化できる/更新側のドメインルールを守りやすい/EDA と相性が良い | 読み書き 2 つのモデル運用で複雑化/結果整合性への理解が必要/小規模では過剰 |
| 2010 年代後半~ | コンポーネント指向 | モジュール/パッケージ | チーム境界と合わせやすい/テスト・置換単位が明確/Modular Monolith と相性が良い | 境界設計が難しい(依存がにじむ)/共通化しすぎると結局密結合に戻ってしまう |
💡 各時代の変遷
コード構造の進化も、前の時代の課題への対応である
- MVCの課題 → Model の責務が曖昧になりがち → レイヤードアーキテクチャで層分離を明確化
- レイヤードの課題 → ドメインが薄くなりがち → 依存制御系アーキテクチャでドメインを中心に保護
- 同期処理の課題(スケール・結合) → イベント駆動で疎結合化
- 横断的な変更の課題 → Vertical Slice で機能単位に閉じる
- 読み書きの競合 → CQRS で分離して最適化
補足
DDD(Domain-Driven Design)と構造パターンの関係:
- DDD(2003 年、Eric Evans 提唱)は「ドメイン(業務)を中心に設計する」思想であり、アーキテクチャパターンではない
- レイヤードアーキテクチャの「ドメインが薄くなる」「DB に支配される」問題を認識させた
- クリーン/オニオン/Hexagonal は、DDD の「ドメインを守る」思想を構造的に実現するための手法として相性が良い
- つまり、**DDD は「何を大事にするか」、構造パターンは「どう守るか」**の関係
依存制御系アーキテクチャの本質:
- 従来のレイヤードは「上位が下位(DB など)に引っ張られがち」であった。クリーンアーキテクチャなどの「依存制御系」が変えたのは、「DB や外部 API は、ビジネスロジックに従属するプラグインに過ぎない」というパワーバランスの逆転である
- クリーン/Hexagonal/Onion は「同じ思想ファミリー」である。違いは"図の描き方"で、狙いは 依存方向を内側(ドメイン)へ向けること
その他の注意点:
- EDA は万能ではない。運用(監視・トレーシング)と設計(冪等性・整合性)がセットである
- CQRS は"必須パターン"ではなく、読み取り負荷やモデルの複雑性がボトルネックになった時の解として有効である
- 2026 年の本質:AI エージェント連携・自律性を前提としたアーキテクチャへのシフトが始まっている
2.1 【2026 年最新】AI ネイティブ時代のアーキテクチャ変化
2026 年現在、システムアーキテクチャは AI エージェント連携を前提とした設計 へと急速にシフトしている。Gartner Top Trends でも示されているように、従来のモノリス/マイクロサービス論だけでは不十分な時代に入った。
2.1.1 AI ネイティブアーキテクチャの台頭
なぜ生まれたか: 2024-2025 年の LLM 普及により、システムが「人間の指示を待つ」から「AI が自律的に判断・実行する」へ変化。従来の RESTful API だけでは、AI エージェント間の複雑な協調動作に対応できなくなった。
主要な要素:
-
Multiagent Systems(マルチエージェントシステム):
- 複数の AI エージェントが協調して問題を解決
- 例:カスタマーサポートエージェント + 在庫確認エージェント + 決済エージェント
- 従来のマイクロサービスとの違い:エージェント間で「交渉」「委譲」「学習」が発生
-
AI-Native Platforms(AI ネイティブプラットフォーム):
- AI の推論・学習をシステムの中核に組み込む
- 例:ユーザーの行動をリアルタイムで学習し、UI を動的に最適化
- 従来のアーキテクチャとの違い:「設計時に決めた仕様」ではなく「実行時に AI が最適化」
-
Domain-Specific Models(ドメイン特化型モデル):
- 汎用 LLM ではなく、ドメイン特化型の小型モデルを組み込む
- 例:医療診断特化モデル、法務文書特化モデル
- メリット:レイテンシ削減、コスト削減、プライバシー保護
-
Confidential Computing(機密コンピューティング):
- AI モデルの推論を暗号化された環境で実行
- なぜ必要か:機密データ(医療・金融)を AI で処理する際、データ漏洩を防ぐ
実装例(Python + LangChain):
# マルチエージェントシステムの簡易実装例
from langchain.agents import AgentExecutor, create_openai_functions_agent
from langchain.tools import Tool
# 在庫確認エージェント
def check_inventory(product_id: str) -> dict:
# 実際はDBアクセス
return {"product_id": product_id, "stock": 10}
# 決済エージェント
def process_payment(amount: float) -> dict:
# 実際は決済API呼び出し
return {"status": "success", "transaction_id": "tx_123"}
# ツール定義
tools = [
Tool(name="CheckInventory", func=check_inventory, description="在庫を確認"),
Tool(name="ProcessPayment", func=process_payment, description="決済処理"),
]
# エージェント実行
agent = create_openai_functions_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools)
# AIが自律的にツールを選択・実行
result = agent_executor.invoke({"input": "商品ID:ABC123を購入したい"})
ディレクトリ構造例(AI ネイティブアーキテクチャ):
src/
├── agents/ # AIエージェント層
│ ├── customer_support/
│ │ ├── agent.py # エージェント定義
│ │ ├── tools.py # 利用可能なツール
│ │ └── prompts.py # プロンプトテンプレート
│ ├── inventory/
│ └── payment/
├── models/ # ドメイン特化モデル
│ ├── fine_tuned/ # ファインチューニング済みモデル
│ └── embeddings/ # ベクトル埋め込み
├── orchestration/ # エージェント間調整
│ ├── coordinator.py # エージェント協調制御
│ └── event_bus.py # エージェント間イベント
├── domain/ # 従来のドメインロジック
│ ├── order/
│ └── inventory/
└── infrastructure/
├── vector_db/ # ベクトルDB(RAG用)
└── model_registry/ # モデル管理
適用場面:
- カスタマーサポートの自動化(複雑な問い合わせ対応)
- 動的な価格最適化(需要予測 + 在庫状況 + 競合分析)
- コード生成・レビューの自動化(GitHub Copilot 的な統合)
課題:
- エージェントの「暴走」をどう防ぐか(ガードレール設計)
- 推論コストの管理(無限ループ防止)
- 説明可能性(なぜその判断をしたか)
- モデルのバージョン管理と A/B テスト
2.1.2 従来アーキテクチャとの共存
重要: AI ネイティブアーキテクチャは、従来の Modular Monolith や Vertical Slice を「置き換える」のではなく、**「拡張する」**関係にある。
- ドメインロジックは従来通り守る(DDD/クリーンアーキテクチャ)
-
AI エージェントは「ユースケース層」の上に乗る
- エージェントがユースケースを呼び出す形
- ドメインルールは AI に任せず、コードで保証
実装パターン:
# ドメイン層(従来通り)
class Order:
def __init__(self, items: list[OrderItem]):
self.items = items
def calculate_total(self) -> Decimal:
# ビジネスルールはコードで保証
return sum(item.price * item.quantity for item in self.items)
def can_apply_discount(self, user: User) -> bool:
# 割引ルールもコードで明示
return user.is_premium and self.calculate_total() > 10000
# エージェント層(AIが判断)
class OrderAgent:
def __init__(self, llm, order_usecase):
self.llm = llm
self.order_usecase = order_usecase
async def handle_customer_request(self, message: str):
# AIが意図を解釈
intent = await self.llm.parse_intent(message)
# ドメインロジックは従来のユースケースを呼び出す
if intent.type == "create_order":
order = self.order_usecase.create_order(
user_id=intent.user_id,
items=intent.items
)
return f"注文を作成しました。合計: {order.calculate_total()}円"
3. 最近のキーワードを整理
3.0 MVCアーキテクチャの基本理解
MVC = Model-View-Controller は1970年代に生まれた ユーザーインターフェース設計のパターン です。
- Model:データとビジネスロジックを担当
- View:ユーザーインターフェース(画面表示)を担当
- Controller:ModelとViewの仲介を担当
重要なポイント: MVCは 「UIパターン」 であり、システム全体のアーキテクチャではありません。レイヤードアーキテクチャやクリーンアーキテクチャなどの 「システム全体設計」 と組み合わせて使用されます。
実際の関係性:
- Rails:MVC + ActiveRecordパターン で実装
- Spring Boot:MVC + レイヤードアーキテクチャ で実装
- どちらもMVCをベースにしているが、実装方法が大きく異なる
3.1 レイヤード / クリーン / Hexagonal / Onion の関係
これらは「どれが優れているか」という対立概念ではなく、**「大切なビジネスルール(中身)を、いかに外側の環境(DB や画面)から守るか」**という工夫の歴史である
3.1.1. レイヤード:伝統的な「上から下へ」の設計
もっとも直感的で、多くのフレームワークが基本形として採用している形である
- イメージ: ケーキのように層が重なっている状態
- 構造: ユーザーに近い方(UI)から、データに近い方(DB)へ順番に呼び出す
- なぜ生まれたか: 1990 年代、Web アプリケーションが普及し始めた頃、「画面」「処理」「データ」を分けるシンプルな構造が求められた
初心者が直面する課題:
- DB に支配される: 「まず DB のテーブルを決めてから、それに合わせてコードを書く」という発想になりがち
- テストが重い: ちょっとした計算ロジックを確認したいだけなのに、DB を立ち上げて接続しないとプログラムが動かせない、といった事態が起きる
3.1.2. 「クリーン / Hexagonal / Onion」:中心を守る「内から外へ」の設計
クリーン、Hexagonal、Onion はすべて、**「依存性の逆転(DIP)」**という考え方を使っている
- なぜ生まれたか: 2000 年代後半~ 2010 年代、システムが複雑化し、DB やフレームワークの変更がビジネスロジックに影響を与える問題が顕在化した。「ビジネスルールを守る」ための設計が求められた
核心:なぜ「中心」を守るのか?
初心者の方が理解する最大のポイントは、**「何が一番大事で、何が一番変わりやすいか」**の区別である
-
一番大事(中心):ドメイン
- 例:「ポイントは 100 円につき 1 ポイント付与する」「在庫が 0 なら注文できない」といったビジネスのルール
- これらは、DB の種類が変わっても、画面が Web からスマホアプリに変わっても変わらないはずのルール
-
変わりやすい(外側):インフラ
- DB の種類、Web フレームワーク、外部サービス(決済 API、通知 SDK など)
- これらは技術の流行やプロジェクトの都合で変わる可能性があるもの
この「大事なもの」を「変わりやすいもの」から切り離して、外側が内側に依存するように向きを逆転させたのが、クリーン、Hexagonal、Onion の共通点である
3.1.3. 「クリーン / Hexagonal / Onion」の違い
実務的には「同じ思想」と捉えて良いが、それぞれの用語が注目しているポイントは以下の通りである
| 名称 | メインの例え | 注目しているポイント |
|---|---|---|
| Clean | 境界線(Boundary) | 「ロジックの純粋さ」。 フレームワークの都合が 1 行も混じっていない、純粋なプログラムの塊を中心に作ることを重視。 |
| Hexagonal | USB ポートとプラグ | 「つなぎ込み」の自由さ。 本体に「ポート(口)」を定義し、DB や API は「アダプター(差し替え可能なプラグ)」とみなす(Ports & Adapters)。 |
| Onion | 玉ねぎの皮 | 「同心円状の保護」。 外側の皮を剥いても中心(ドメイン)は傷つかない。依存は必ず「外から内」の一方向であることを強調。 |
3.1.4. 結局、初心者にとって何が嬉しいの?
「将来 DB を変えることなんてないから、レイヤードで十分では?」と思うかもしれない。しかし、クリーン / Hexagonal / Onion を採用すると**開発中の「今」**の生産性が向上しやすい
-
「業務に直結したビジネスルールを書いている」自覚
- DB の保存処理と計算処理が混ざらないので、コードの保守性が劇的に上がる
-
DB がなくても、先にロジックが書ける
- 「DB の準備ができるまで実装待機」が必要ない。中心のロジックだけ先に書いて、テストで動作確認を済ませることができる
-
テストコードが高速化する
- DB への接続待ちがなくなるため、何百個というテストが数秒で終わるようになり、開発効率が向上する
まとめ:使い分けの判断基準
クリーン / Hexagonal / Onion を選ぶとき
- DB 移行や外部 API の差し替えが予想される
- ビジネスロジックが複雑で、テストコードをしっかり書きたい
レイヤード を選ぶとき
- 小規模なツールや、変更がほとんどないシステム
- スピード重視で、複雑な抽象化(インターフェース定義など)を避けたい
最初から完璧なクリーンアーキテクチャを目指す必要はない
まずは「大事な計算ロジックの中に、SQL 文や外部 API 呼び出しを混ぜない」この 1 点を意識するだけで、コードの保守性が上がる
3.2. モダンなモノリスの詳細
マイクロサービスは「技術的な解決」以上に「組織的な解決」の側面が強い。「少数のチームが多数のサービスを運用する」または「サービスの境界とチームの境界が一致しない」状態では、マイクロサービスのメリットを損ない、デメリットだけを受ける状態になりがち。運用体制が整っていない状況で分散システムを導入した結果、複雑性だけが増大する恐れがある
- **狙い:**分散の複雑性(運用・整合性)を避けつつ、モノリスの弱点(肥大化)を"内部境界"で抑える
特徴:
- モジュール間の依存方向を制御(片方向/公開 API のみ)
- DB を物理的に共有しても良いが、モジュール間では直接テーブルを参照せず、公開 API 経由でアクセスする(論理的な境界を守る)
- "機能単位(Vertical Slice)"でモジュールを切ると相性が良い
適用場面:
- サービス分割したいが、SRE 体制や可観測性が追いついていない
- まずは変更影響を局所化したい(リリース頻度を上げたい)
3.3 イベント駆動アーキテクチャ(EDA)と CQRS のつながり
EDA と CQRS は、セットで語られることが増えている
- なぜ生まれたか: 2010 年代、リアルタイム処理やスケーラビリティの要求が高まり、同期的な処理では限界が見えてきた。また、読み取りと書き込みで最適化の方向性が異なることが明確になった
EDA:
- 変更や出来事(例:注文確定、発送完了)をイベントとして発行
- 関心のあるサービス/モジュールが購読して処理
CQRS:
- 書き込み(Command)と読み取り(Query)を分け、読み取りを最適化
- 読み取り用のモデル(Read Model)をイベントで更新すると相性が良い
注意点(設計上の考慮事項):
- 冪等性(同じイベントが複数回届いても、結果が同じになるように設計する)
- 再送/順序入れ替わり/遅延
- 可観測性(トレース ID、相関 ID、イベントログ)
3.4 DDD(ドメイン駆動設計)
DDD は 2003 年に Eric Evans が著書「Domain-Driven Design」で体系化した設計手法である
- 狙い: ビジネスの複雑さをコードで表現し、ドメイン知識をチームで共有する
- なぜ生まれたか: 2000 年代、エンタープライズシステムが大規模化する中で、ビジネスルールが DB スキーマや UI に散らばり、保守が困難になった。ビジネスの本質をコードに表現する手法が求められた
主要な概念:
- エンティティ: ID で識別されるオブジェクト(例:ユーザー、注文)
- 値オブジェクト: 属性で識別される不変オブジェクト(例:金額、住所)
- 集約: 関連するオブジェクトのまとまり。一貫性の境界を定義する
- 境界づけられたコンテキスト: モデルが有効な範囲を明確にする
- ユビキタス言語: 開発者とドメイン専門家が共通の言葉で話す
適用場面:
- ビジネスルールが複雑で頻繁に変更される
- ドメイン専門家と開発者の協業が重要
- 長期間保守されるシステム
3.5 SOA(サービス指向アーキテクチャ)
SOA は 2000 年代に普及した、サービス単位でシステムを連携させるアーキテクチャである
- 狙い: 既存システムをサービスとして連携させ、全社レベルで再利用する
- なぜ生まれたか: 大企業で部門ごとに異なるシステムが乱立し、データ連携が課題に。標準化された方法でシステム間連携を実現する必要があった
主要な要素:
- SOAP: XML ベースの通信プロトコル
-
ESB(Enterprise Service Bus): サービス間の中継ハブ。メッセージ変換やルーティングを担当するが、ビジネスロジックが ESB に集中しやすい(スマートパイプ問題)
-
スマートパイプ問題とは:
ESB(中継ハブ)にビジネスロジックを実装してしまうと、「どこで何が処理されているか」が見えにくくなり、ESB 自体が複雑化・肥大化する問題。マイクロサービスでは「ダムパイプ(単純な通信路)、スマートエンドポイント(各サービスが賢い)」という逆の思想を採用している
-
スマートパイプ問題とは:
- WSDL: サービスのインターフェース定義
課題:
- 規格が重く、開発・運用コストが高い
- ESB が単一障害点・ボトルネックになりやすい
- マイクロサービスの登場で、より軽量な REST API が主流に
3.6 イベント駆動アーキテクチャ(EDA)
EDA はシステム内の出来事(イベント)を中心に設計するアーキテクチャである
- 狙い: サービス間の疎結合を実現し、非同期処理でスケーラビリティを向上させる
- なぜ生まれたか: 2010 年代、リアルタイム処理や大規模トラフィックへの対応が必要に。同期的な API 呼び出しではスケールや障害分離に限界があった
主要な要素:
- イベント: システム内で発生した出来事(例:注文確定、発送完了)
- Publisher/Subscriber: イベントを発行する側と購読する側
- メッセージブローカー: Kafka、RabbitMQ など
課題:
- 処理の因果関係を追跡するのが難しい
- 冪等性(べきとうせい)の設計が必須(同じイベントが複数回届いても、結果が同じになるように設計する)
- データの最終的な整合性を許容する必要がある
3.7 Vertical Slice Architecture
Vertical Slice は機能単位でコードを分割するアプローチである
- 狙い: 1 つの機能の追加・修正が 1 箇所で完結するようにする
- なぜ生まれたか: 2015 年頃から、レイヤードアーキテクチャでは機能追加時に複数の層をまたいで修正する必要があり、認知負荷が高かった。Jimmy Bogard が MediatR と共に提唱・普及
特徴:
- 機能(Feature)ごとにフォルダを分ける
- 各機能が UI から DB までの全層を持つ
- 機能間でコードが重複することを許容する
【2026 年の進化】AI 開発ツールとの統合:
- GitHub Copilot や Cursor などの AI ツールと相性が良い
- 機能が 1 箇所にまとまっているため、AI がコンテキストを理解しやすい
- 「この機能を真似して新しい機能を作って」が簡単にできる
ディレクトリ構造例(Python + FastAPI):
src/
├── features/ # 機能ごとにフォルダを分ける
│ ├── orders/ # 注文関連の機能をまとめる
│ │ ├── create_order/ # 「注文作成」機能
│ │ │ ├── command.py # リクエストの定義
│ │ │ ├── handler.py # ビジネスロジック
│ │ │ └── endpoint.py # API エンドポイント
│ │ ├── get_order/ # 「注文取得」機能
│ │ │ ├── query.py
│ │ │ ├── handler.py
│ │ │ └── endpoint.py
│ │ └── cancel_order/ # 「注文キャンセル」機能
│ ├── products/ # 商品関連の機能
│ │ ├── list_products/
│ │ └── update_stock/
│ └── payments/ # 決済関連の機能
├── shared/ # 全機能で共通利用するコード
│ ├── dependencies.py # FastAPI の依存性注入
│ ├── exceptions.py # カスタム例外
│ └── validators.py # 共通バリデーション
└── infrastructure/ # DB接続などの基盤
└── database.py
実装例(Python + FastAPI):
1. コマンド定義(command.py)
# features/orders/create_order/command.py
from dataclasses import dataclass
from typing import List
# 注文の商品1つ分のデータ
@dataclass
class OrderItemDto:
"""注文商品のデータ転送オブジェクト
Attributes:
product_id: 商品ID
quantity: 注文数量
"""
product_id: str
quantity: int
# 注文作成のリクエスト
@dataclass
class CreateOrderCommand:
"""注文作成コマンド
「注文を作りたい」というリクエストを表現するクラス。
必要な情報(誰が、何を)をまとめて持つ。
Attributes:
user_id: 注文するユーザーのID
items: 注文する商品のリスト
"""
user_id: str
items: List[OrderItemDto]
初心者向け解説:
-
@dataclass:データを入れる箱を簡単に作れる Python の機能 -
CreateOrderCommand:「注文を作成する」というリクエストそのもの - なぜ分けるか?:リクエストの形を明確にすることで、何が必要かが一目でわかる
2. ハンドラー(handler.py)
# features/orders/create_order/handler.py
from datetime import datetime
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
class CreateOrderHandler:
"""注文作成の処理を担当するクラス
このクラスが「注文作成」のビジネスロジックを全て持つ。
DB操作、バリデーション、データ変換など、この機能に必要な
処理が全てここに集約される。
"""
def __init__(self, db: AsyncSession):
"""初期化
Args:
db: データベースセッション(FastAPIから自動で渡される)
"""
self.db = db
async def handle(self, command: CreateOrderCommand) -> dict:
"""注文作成の実行
Args:
command: 注文作成コマンド(ユーザーIDと商品リスト)
Returns:
作成された注文の情報(ID、ユーザーID、商品数)
Raises:
ValueError: ユーザーが存在しない場合
"""
# ステップ1: ユーザーが存在するか確認
user = await self.db.get(User, command.user_id)
if not user:
# ユーザーが見つからない場合はエラー
raise ValueError("User not found")
# ステップ2: 注文データを作成
order = Order(
user_id=command.user_id,
# リスト内包表記で商品リストを変換
items=[
OrderItem(
product_id=item.product_id,
quantity=item.quantity
)
for item in command.items
],
created_at=datetime.utcnow() # 現在時刻を記録
)
# ステップ3: データベースに保存
self.db.add(order) # 保存予約
await self.db.commit() # 実際に保存を実行
await self.db.refresh(order) # 保存後のデータを取得(IDなど)
# ステップ4: レスポンスを返す
return {
"id": order.id,
"user_id": order.user_id,
"item_count": len(order.items)
}
解説:
-
async/await:非同期処理。複数のリクエストを効率的に処理できる -
handleメソッド:この機能の「本体」。ここに全てのロジックが書かれている - なぜ 1 箇所に?:関連するコードが散らばらないので、読みやすく修正しやすい
3. エンドポイント(endpoint.py)
# features/orders/create_order/endpoint.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from shared.dependencies import get_db
# このファイル専用のルーター(URLの設定)
router = APIRouter(
prefix="/api/orders", # URLの先頭部分
tags=["Orders"] # ドキュメントでのグループ名
)
@router.post("/", status_code=201)
async def create_order(
command: CreateOrderCommand, # リクエストボディから自動変換
db: AsyncSession = Depends(get_db) # DBセッションを自動注入
):
"""注文作成API
POST /api/orders にリクエストが来たときに実行される。
Args:
command: 注文作成コマンド(JSONから自動変換される)
db: データベースセッション(FastAPIが自動で渡す)
Returns:
作成された注文の情報
Raises:
HTTPException: エラーが発生した場合
"""
try:
# ハンドラーを作成して実行
handler = CreateOrderHandler(db)
result = await handler.handle(command)
return result
except ValueError as e:
# ビジネスエラー(ユーザーが存在しないなど)
raise HTTPException(status_code=404, detail=str(e))
except Exception as e:
# 予期しないエラー
raise HTTPException(status_code=500, detail="Internal server error")
解説:
-
@router.post:「POST /api/orders」という URL を定義 -
Depends(get_db):FastAPI の依存性注入。DB セッションを自動で渡してくれる -
HTTPException:エラーを HTTP ステータスコードに変換
4. 共通処理(バリデーション)
# shared/validators.py
from functools import wraps
from fastapi import HTTPException
def validate_command(func):
"""コマンドのバリデーションを行うデコレーター
全ての機能で使える共通のバリデーション処理。
関数の前に自動でチェックを実行する。
"""
@wraps(func)
async def wrapper(command, *args, **kwargs):
# 基本的なバリデーション
if hasattr(command, 'user_id') and not command.user_id:
raise HTTPException(status_code=400, detail="user_id is required")
if hasattr(command, 'items') and not command.items:
raise HTTPException(status_code=400, detail="items cannot be empty")
# 元の関数を実行
return await func(command, *args, **kwargs)
return wrapper
# 使い方の例
# @validate_command
# async def create_order(command: CreateOrderCommand, db: AsyncSession):
# ...
解説:
- デコレーター:関数の前後に処理を追加する仕組み
- 共通処理:全機能で使えるので、コードの重複を減らせる
-
@wraps:元の関数の情報を保持するためのおまじない
実際の使い方(リクエスト例)
# クライアントから送られるJSONの例
{
"user_id": "user123",
"items": [
{"product_id": "prod001", "quantity": 2},
{"product_id": "prod002", "quantity": 1}
]
}
# レスポンスの例
{
"id": "order456",
"user_id": "user123",
"item_count": 2
}
従来のレイヤードアーキテクチャとの比較
従来の方法(レイヤード):
controllers/
order_controller.py # 全ての注文関連のエンドポイント
services/
order_service.py # 全ての注文関連のビジネスロジック
models/
order.py # 注文のデータモデル
機能を追加するとき、3 つのファイルを行ったり来たりする必要がある。
Vertical Slice:
features/orders/
create_order/ # 「注文作成」に必要なものが全部ここ
command.py
handler.py
endpoint.py
get_order/ # 「注文取得」に必要なものが全部ここ
query.py
handler.py
endpoint.py
1 つの機能を追加・修正するとき、1 つのフォルダだけを見れば良い。
コードの流れ(図解)
1. クライアント
↓ POST /api/orders (JSON)
2. endpoint.py
↓ CreateOrderCommand に変換
3. handler.py
↓ ビジネスロジック実行
├─ ユーザー確認
├─ 注文データ作成
└─ DB保存
4. レスポンス
↓ JSON形式で返却
クライアント
各ファイルの役割まとめ:
| ファイル | 役割 | 例え |
|---|---|---|
| command.py | リクエストの形を定義 | 注文用紙のフォーマット |
| handler.py | 実際の処理 | 注文を受けて調理する人 |
| endpoint.py | 入口(URL) | レストランの入口 |
メリット:
- 機能追加時の影響範囲が明確 - 1 つのフォルダだけ見れば良い
- 並行開発がしやすい - 別々の機能なら、別々のフォルダで作業できる
- 認知負荷が低い - 関連するコードが近くにあるので理解しやすい
- AI ツールが機能全体を理解しやすい - 1 つのフォルダに全てがあるので、AI が文脈を把握しやすい
- テストが書きやすい - 機能ごとに独立しているので、テストも独立して書ける
課題:
- 横断関心事(認可、ログ等)の共通化に工夫が必要 - 全機能で使う処理は shared/ に置く
- 似たコードが増える - 機能ごとに似たコードができるが、それを許容する(無理に共通化しない)
- 初期の学習コスト - 従来の方法に慣れている人には、最初は違和感がある
Python での実装のポイント
-
型ヒント(Type Hints)を活用
-
command: CreateOrderCommandのように型を明示すると、IDE が補完してくれる - FastAPI が自動でバリデーションしてくれる
-
-
async/await を使う
-
async defとawaitで非同期処理を実現 - 複数のリクエストを効率的に処理できる
-
-
依存性注入(Dependency Injection)
-
Depends(get_db)で DB セッションを自動で渡す - テスト時にモックに差し替えやすい
-
-
dataclass を使う
-
@dataclassでデータクラスを簡単に定義 - 自動で
__init__や__repr__が生成される
-
まとめ:Vertical Slice Architecture
Vertical Slice Architecture は「機能ごとにコードをまとめる」シンプルな考え方です。
こんな人におすすめ:
- 機能追加が頻繁にあるプロジェクト
- 複数人で並行開発するチーム
- AI 開発ツール(Copilot、Cursor など)を活用したい人
- コードの見通しを良くしたい人
始め方:
- まずは 1 つの機能を
features/フォルダに作ってみる - 従来の方法と比べて、どちらが理解しやすいか確認
- 良さそうなら、他の機能も徐々に移行
完璧を目指さず、「関連するコードを近くに置く」という原則だけ守れば、自然と保守しやすいコードになります。
3.8 CQRS(Command Query Responsibility Segregation)
CQRS は更新処理と読み取り処理を分離するパターンである
- 狙い: 読み取りと更新で最適化の方向性が異なるため、モデルを分離してそれぞれを最適化する
- なぜ生まれたか: 2010 年代、読み取り負荷が高いシステムで、更新用の正規化された DB モデルでは読み取りパフォーマンスが出ない。Greg Young が体系化
主要な要素:
- Command(更新): ドメインモデルを使ってビジネスルールを守る
- Query(読み取り): 読み取り専用に最適化された Read Model を使う
メリット:
- 読み取り性能を最大化できる
- 更新側のドメインルールを守りやすい
- EDA と相性が良い
課題:
- 読み書き 2 つのモデル運用で複雑化
- 結果整合性への理解が必要
- 小規模では過剰
3.9 イベントソーシング(Event Sourcing)
イベントソーシングは、データの「現在の状態」ではなく「変更の履歴(イベント)」を記録する手法である
- 狙い: すべての変更履歴を保持し、任意の時点の状態を再現可能にする
- なぜ生まれたか: 2010 年代、監査要件や複雑なビジネスルールの追跡が必要になり、「なぜこの状態になったか」を記録する必要性が高まった。Greg Young が CQRS と共に体系化
主要な概念:
- イベントストア: すべてのイベントを時系列で保存するデータベース
- イベントの再生: 過去のイベントを順に適用して、現在の状態を復元する
- スナップショット: パフォーマンス向上のため、特定時点の状態を保存しておく
CQRS との関係:
- イベントソーシングと CQRS は別の概念だが、組み合わせると相性が良い
- イベントソーシングで記録したイベントから、CQRS の Read Model を構築できる
- ただし、CQRS はイベントソーシングなしでも実装可能(通常の DB で Command/Query を分離するだけでも有効)
メリット:
- 完全な監査ログが自動的に残る
- 過去の任意の時点の状態を再現できる
- バグ修正時に過去データを再計算できる
課題:
- イベントスキーマの変更が難しい(バージョニングが必要)
- イベント数が増えると再生に時間がかかる(スナップショットで対応)
- 学習コストが高く、小規模では過剰
3.10 コンポーネント指向アーキテクチャ(Component-Based Architecture)
"クラス"や"層"ではなく、変更単位=コンポーネントでシステムを分ける発想である
- なぜ生まれたか: 大規模システムで、「誰がどこを担当しているか」が不明確になり、変更時の影響範囲が読めない問題が発生した。コンポーネント単位で責任を明確にする必要性が高まった
メリット:
- コンポーネントごとにオーナー(責任範囲)を持てる
- テスト・デプロイの単位を明確にしやすい(特に Modular Monolith で有効)
課題:
- 共通化(ユーティリティ)をやりすぎると、依存が絡まって"1 枚岩化"する
4. 【余談】Rails は"フレームワークが内包するアーキテクチャ"として整理すると分かりやすい
Rails は単なる MVC ではなく、思想がセットになっている
- MVC: モデル/ビュー/コントローラの責務分離
-
Convention over Configuration(設定より規約):
- 決まりごとに従えば、設定を最小にして高速に開発できる
-
The Rails Way:
- 「こう作ると気持ちいい」をフレームワーク側が強く提示する
Rails をこの記事の枠組みに当てはめると
- Rails は MVC を中心に、ActiveRecord/規約により“レイヤード的”な責務分割になりやすい
よくある注意点
- 規模が上がると「責務の置き場」が難しくなり、
- Fat Model / Fat Controller になりやすい
- そこで Form Object / Query Object / Service(または UseCase) などの整理が必要になる
✅Rails は「アーキテクチャの一種」というより、**アーキテクチャの"運用ルール込みの実装セット"**として捉えると整理しやすい
5. 変遷のまとめ(課題 → 解決)
システム形態の変遷
- 集中型(メインフレーム): 一貫性は強いが高価で遅い → 課題:コスト・スピード
- C/S: リッチ UI は得たが、配布/更新が困難 → 課題:運用負荷
- Web: 配布は解決したが、アプリ肥大化・調整コスト増 → 課題:肥大化
- エンタープライズ: フレームワークで効率化したが、単一デプロイで影響範囲が広い → 課題:変更の影響範囲
- マイクロサービス: 独立デプロイで組織とスケール問題に対処するが、運用が難しい → 課題:分散の複雑性
- サーバレス/マネージド: 運用負荷を減らすが、制約と依存(ロックイン)が増える → 課題:ベンダー依存
- Modular Monolith 再評価: 分散の複雑性を避けつつ、内部境界で保守性を確保 → 現在の解
コード構造の変遷
- レイヤードで責務分離 → 変更が散らかる → 課題:影響範囲
- DDD/依存制御でドメインを守る → 抽象が増える → 課題:学習コスト
- Vertical Slice/Component で変更を閉じる → 共通化の設計が必要 → 課題:重複
- EDA/CQRS でスケール・最適化 → 整合性・運用が難しくなる → 課題:複雑性
共通するパターン: 各時代のアーキテクチャは、前の時代の課題を解決するために生まれ、新たなトレードオフを生み出している
6. 用語集
- Modular Monolith: デプロイは 1 つ、内部は境界で分割し依存を制御
- Hexagonal(ポート&アダプター): 外部接続点(Port)と実装(Adapter)を分離し差し替えやすくする
- EDA: 出来事(Event)を発行し、購読者が非同期に処理する
- CQRS: Command(更新)と Query(参照)を分離する
- Component-Based: 変更単位(コンポーネント)で責務と依存を管理する
7. まとめ
-
アーキテクチャは「流行の正解」を選ぶものではなく、プロジェクトの制約(規模・変更頻度・運用体制・チーム構成・求める品質)に合わせて最適解を選ぶもの
-
モノリスが劣っているわけでも、トレンドな構成が常に優れているわけでもない
- それぞれにメリット・デメリット、得意・不得意(トレードオフ)があり、状況次第で"ベスト"は変わる
-
各時代のアーキテクチャは、前の時代の課題を解決するために生まれた
- この歴史を理解することで、自分のプロジェクトに何が必要かが見えてくる
-
だからこそ、最初から流行やトレンドのパターンに飛びつくのではなく、
- 何を実現したいのか、何が課題なのかを明確にして、ケースバイケースで選定することが重要
8. 【2026 年の展望】AI ネイティブ時代のアーキテクチャ選定
8.1 従来アーキテクチャの位置づけ
2026 年現在、従来のアーキテクチャパターン(Modular Monolith、Vertical Slice、DDD)は依然として重要である。ただし、その上にAI エージェント層が追加される形で進化している。
推奨される構成:
┌─────────────────────────────────┐
│ AIエージェント層 │ ← 2026年の新要素
│ (Multiagent Systems) │
├─────────────────────────────────┤
│ ユースケース層 │ ← 従来通り
│ (Application Services) │
├─────────────────────────────────┤
│ ドメイン層 │ ← 従来通り(最重要)
│ (Business Rules) │
├─────────────────────────────────┤
│ インフラ層 │ ← 従来通り + Vector DB
│ (DB, External APIs) │
└─────────────────────────────────┘
8.2 2026 年の実務における考察
AI 中心のプロダクト:
- AI ネイティブアーキテクチャ を採用
- ドメインロジックは依然としてコードで保証
- エージェント間の協調が複雑な場合、Event-Driven Architecture と組み合わせる
8.3 避けるべき落とし穴(2026 年版)
❌ 「AI があれば設計不要」という誤解
- AI はビジネスルールを「解釈」するが、「保証」はできない
- 重要なルール(金額計算、在庫管理)は必ずコードで実装
❌ 「最新だからマルチエージェント」という飛びつき
- 単純な CRUD にマルチエージェントは過剰
- まずは単一エージェント + 従来アーキテクチャで十分
❌ 「モノリスは古い」という思い込み
- 2026 年でも Modular Monolith は主流の選択肢
- Shopify、GitHub、Basecamp など大手も採用継続
8.5 最後に
2026 年のアーキテクチャ選定は、「AI と従来技術の適切な組み合わせ」 がカギである。
- AI は「自律性」と「柔軟性」をもたらすが、「確実性」は従来のコードが保証する
- Modular Monolith は「シンプルさ」を保ちながら「保守性」を確保する
- Vertical Slice は「AI 開発ツール」との相性が良く、生産性を高める
トレンドに流されず、プロジェクトの制約と目的に合わせて選定することが、2026 年も変わらず重要である。