2024年のvFunction調査によると、50%以上の企業がIT予算の4分の1以上を技術的負債に費やしているとされています。
これは、開発者がコードを書くのが下手だから起きているわけではありません。私たちのアーキテクチャ境界が侵食されているからです。ビジネスルールがデータベースモデルに染み込み、HTTPコントローラーがコアロジックに染み込んでいるのです。
主な原因は?最新のフレームワークマーケティングと、私たちがコードの書き方を学ぶ「チュートリアル文化」です。
この記事では、なぜアーキテクチャが劣化しているのか、私たちが侵害しているエンジニアリング原則、そして六角形分離がどのように構造的な解決策を提供するのかを深く掘り下げます。完全な動作するTypeScriptコードベース、FlowBank を提供します。
1. 罠:「Time-to-Hello-World」とアーキテクチャ的負債
ほとんどの開発者は、15分の動画チュートリアルやフレームワークのクイックスタートを通じてアプリケーションの構築方法を学びます。採用を勝ち取るため、フレームワークは迅速なオンボーディングに最適化されており、「魔法」のショートカットを使って3ステップでTodoアプリを構築する方法を示しています。
これにより3つのアーキテクチャトラップが生まれます:
- Active Recordトラップ — ORMはデータベーススキーマをビジネスエンティティに直接結合します。テーブルを変更すると、コアビジネスモデルが壊れます。
- Controller-to-DBショートカット — フレームワークのボイラープレートは、HTTPハンドラ内に直接バリデーション、ビジネスルール、SQLを書くことを推奨します。
- 「魔法」のアノテーションブリード — ドメインオブジェクトに直接注入されるフレームワーク固有のデコレータは、アプリケーション全体をそのベンダーに人質にします。
これらのショートカットを十分に長く続けると、アーキテクチャ的技術的負債(ATD)が蓄積します。3年後、フレームワークをアップグレードしたり、データベースプロバイダを切り替えたりすると、会社の基盤となるビジネスルールを書き換えることになります。
2. 解決策:六角形分離とは?
元々はAlistair CockburnによってPortsとAdaptersとして提唱された六角形アーキテクチャは、レイヤーの染み込みを解決するために厳格な構造的分離を強制します。
[External World] ----> ( Port / Interface ) ----> [Core Business Logic]
(HTTP/DB/CLI) (Strict Abstraction) (Pure Domain Rules)
Enter fullscreen mode Exit fullscreen mode
- コアドメイン — 純粋なビジネスロジックのみを含みます。Web、データベース、ファイルシステム、フレームワークに関する知識は一切ありません。
- ポート(抽象インターフェース) — 六角形の境界は完全に抽象インターフェースで構築されます。コアはこのインターフェースとしか通信しません。
- アダプタ(具体的な実装) — Postgres、REST API、GraphQL、S3 — すべて六角形の外側に存在し、データを出し入れするためのポートを実装します。
抽象化を通じてコアを分離することで、真のプラグアンドプレイアーキテクチャが実現します。コアアプリケーションルールに1行も触れることなく、MongoDBをPostgreSQLに置き換えることができます。
最小限のTypeScript例
これはFlowBank全体で使用されるパターンの簡略版です。FlowBankは、転送、入金、通知、支払い処理など、すべてコアがPostgres、Stripe、Expressから完全に分離された状態で、このアーキテクチャをエンドツーエンドで実証するために特別に構築された、小さなオープンソースの銀行APIです。
// core/ports/user-repository.port.ts
// The PORT — an abstract interface the core depends on, not a concrete DB.
export interface UserRepository {
findById(id: string): Promise<User | null>;
save(user: User): Promise<void>;
}
// core/domain/user.ts
// The CORE — pure business logic, no framework or DB imports.
export class User {
constructor(public readonly id: string, private email: string) {}
changeEmail(newEmail: string) {
if (!newEmail.includes("@")) {
throw new Error("Invalid email");
}
this.email = newEmail;
}
}
// adapters/postgres-user-repository.ts
// The ADAPTER — concrete implementation, swappable without touching the core.
import { UserRepository } from "../core/ports/user-repository.port";
import { User } from "../core/domain/user";
import { db } from "./db-client";
export class PostgresUserRepository implements UserRepository {
async findById(id: string): Promise<User | null> {
const row = await db.query("SELECT * FROM users WHERE id = $1", [id]);
return row ? new User(row.id, row.email) : null;
}
async save(user: User): Promise<void> {
await db.query("UPDATE users SET email = $1 WHERE id = $2", [
user, // simplified for brevity
user,
]);
}
}
Enter fullscreen mode Exit fullscreen mode
Userクラスとそのビジネスルールは、db-client、Express、またはPostgres固有のものをインポートすることは決してありません。明日、PostgresUserRepositoryをMongoUserRepositoryやテスト用のInMemoryUserRepositoryに置き換えても、コアを変更する必要はありません。
3. 理論的柱:SOLID & DDD
六角形分離は恣意的な設計選択ではなく、コンピュータサイエンスの基礎原則の具体的な実装です。
SOLIDマトリックス:
- 依存性逆転の原則(DIP) — 高レベルのビジネスロジックは低レベルのフレームワークに依存してはなりません。両方とも抽象化に依存します。六角形アーキテクチャは、すべてのソースコード依存関係がコアに向かって内側を向くことを強制します。
- 単一責任の原則(SRP) — ドメインモデルは、ビジネス要件の変更という単一の変更理由を持ちます。フレームワークの配管は完全に取り除かれています。
- オープン/クローズドの原則(OCP) — 拡張に対してはオープン、修正に対してはクローズド。新しいSMSプロバイダが必要ですか?新しいアダプタを書いてください。コアは変更されません。
ドメイン駆動設計とコンポーネント結合:
六角形分離は、Clean Architecture(Robert C. Martin)やOnion Architecture(Jeffrey Palermo)と同じファミリーに属します。循環参照を排除する非循環依存原則(ADP)を強制し、最も安定したコンポーネントであるビジネスルールをすべての依存関係のターゲットにすることで安定依存原則(SDP)を遵守します。
4. エンジニアリングの成果
六角形分離の実装は、初期段階でより多くのボイラープレートを必要としますが、長期的なROIは大きいものです:
- 鉄壁のテスト容易性 — コアは抽象ポートにのみ依存するため、データベースや外部APIを即座にモックできます。Dockerコンテナやライブサーバーを必要とせずに、ミリ秒単位でビジネスロジック全体をテストできます。
- フレームワーク非依存性 — フレームワークはアプリケーションの定義ではなく、配信メカニズムになります。
- 将来性のある進化 — より高速なデータベースやより安価なクラウドプロバイダが登場した場合、移行コストは完全な書き換えから単一の新しいアダプタの構築に削減されます。
5. 実際のコードベースで確認:FlowBank
パターンについて読むことと、それが実際の依存関係グラフの下で機能することを確認することは、2つの異なる経験です。そのため、この記事にはFlowBankが付属しています。FlowBankは、転送、入金、通知、支払い処理など、実際の金銭移動ワークフローを通じて六角形アーキテクチャを実証するために構築されたオープンソースのTypeScript銀行APIです。
src/
core/ <- the hexagon's interior, zero framework imports allowed
domain/ Account, Money, Transaction — pure business rules
ports/ interfaces the core depends on, never concretes
use-cases/ application logic, orchestrates domain + ports
errors/ domain-specific error types
adapters/ <- everything outside the hexagon
postgres/ real DB implementation of the repository ports
stripe/ real payment gateway implementation
email/ real notification implementation
http/ Express router — a driving adapter, calls IN to use cases
in-memory/ fakes used by tests, swap-in for Postgres/Stripe/email
main.ts <- composition root: the ONLY file allowed to wire
concrete adapters into the use cases
Enter fullscreen mode Exit fullscreen mode
実際に読むだけでなく、試してみる価値のあることがいくつかあります:
-
npm testは、データベース、ネットワーク呼び出し、Dockerコンテナなしで、コアのビジネスルールテストを実行します。PostgresとStripeの代わりにインメモリアダプタが使用されます。 - 約束ではなく証明のためにコードベースを検索:
grep -rl "from.*adapters" src/coreは何も返しません。この記事の前半の図の依存関係矢印は、願望ではなく、実際にインポートされたものによって強制されています。 StripePaymentGatewayを架空のPaystackPaymentGatewayに、またはPostgresAccountRepositoryをMongo相当のものに置き換えてみてください。どちらもAccount、Money、またはユースケースに触れません。これは、この記事の議論全体を具体化したものです。
クローンを作成し、壊し、拡張してください。リポジトリは、これが単なる図ではなく、実際に動作し、高速にテストでき、プロバイダの交換に耐えられるコードベースであることの証明です。
結論
技術的負債は通常、1つの悪い決定としてではなく、1つのフレームワークショートカットずつ蓄積され、最終的にビジネスロジックと配信メカニズムが互いに絡み合って、片方が壊れずに他方を変更できなくなります。六角形分離は、それに対する直接的で構造的な答えです。すべてのフレームワーク、データベース、ベンダーSDKを端に押しやり、ビジネスについてしか知らない、小さな依存関係のないコアを維持します。
成果は理論的なものではありません。数分ではなくミリ秒で実行されるテスト、四半期ではなくスプリントで完了する移行、そしてビジネスルールがORMフックやルートハンドラに埋もれていないため、新人エンジニアが実際に理解できるコードベースとして現れます。
15分のチュートリアルのようにソフトウェアを構築するのをやめましょう。コアを分離し、境界を守り、FlowBankをクローンしてパターンを自分で確認し、エンジニアリング予算を取り戻してください。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.