根据 vFunction 2024 年的调查,超过 50% 的公司眼睁睁地看着超过四分之一的 IT 预算流失到技术债务中。
这并非因为开发者不擅长编写代码,而是我们的架构边界正在消逝。我们让业务规则渗透进数据库模型,让 HTTP 控制器渗透进核心逻辑。
罪魁祸首是什么?现代框架的营销以及指导我们学习编码的“教程文化”。
本文将深入剖析你的架构为何“失血”、我们正在违反哪些工程原则,以及六边形隔离如何提供结构性解决方案——附带一个完整的 TypeScript 代码库 FlowBank,让你亲眼看到实际应用,而非纸上谈兵。
1. 陷阱:“Hello-World 时间”与架构债务
大多数开发者都是通过 15 分钟的视频教程或框架快速入门来学习构建应用程序的。为了赢得采用率,框架优化了快速上手体验——用“魔法”快捷方式教你在三步内构建一个 Todo 应用。
这会制造三种架构陷阱:
- Active Record 陷阱——ORM 将数据库模式直接耦合到业务实体。更改表结构,核心业务模型就会损坏。
- Controller-to-DB 捷径——框架样板代码鼓励在 HTTP 处理器中直接编写验证、业务规则和 SQL。
- “魔法”注解渗透——特定框架的装饰器直接注入到领域对象中,使得整个应用被该供应商绑架。
长期依赖这些捷径会累积 架构技术债务 (ATD)。三年后,升级框架或更换数据库提供商意味着重写公司的基础业务规则。
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——全部位于六边形之外,实现端口以输入输出数据。
通过抽象隔离核心,你将获得真正的即插即用架构。无需修改一行核心应用规则即可将 MongoDB 替换为 PostgreSQL。
一个最小的 TypeScript 示例
这是 FlowBank 中使用的模式精简版。FlowBank 是一个小型开源银行 API,专门用于端到端演示此架构——转账、存款、通知和支付处理,所有核心均与 Postgres、Stripe 和 Express 完全隔离。
// 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)——对扩展开放,对修改关闭。需要新的短信提供商?编写一个新的适配器。核心保持不变。
领域驱动设计与组件耦合:
六边形隔离与 Clean Architecture(Robert C. Martin)和 Onion Architecture(Jeffrey Palermo)属于同一家族。它强制执行无环依赖原则 (ADP),消除循环引用,并通过使最稳定的组件(业务规则)成为所有依赖的目标来遵守稳定依赖原则 (SDP)。
4. 工程回报
实施六边形隔离意味着前期更多的样板代码,但长期回报巨大:
- 牢不可破的可测试性——由于核心仅依赖抽象端口,你可以立即模拟数据库和外部 API。以毫秒级速度测试整个业务逻辑,无需 Docker 容器或实时服务器。
- 框架不可知论——你的框架成为交付机制,而非应用的定义。
- 面向未来的演进——当更快的数据库或更便宜的云提供商出现时,迁移成本从完全重写降至构建一个新的适配器。
5. 真实代码库中的实践: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 验证,而非承诺:
grep -rl "from.*adapters" src/core返回空结果。本文前面的图表中的依赖箭头不是空想——它们由实际导入的内容强制执行。 - 尝试将
StripePaymentGateway替换为假设的PaystackPaymentGateway,或将PostgresAccountRepository替换为 Mongo 等效实现。两者均不触及Account、Money或用例——这正是本文论点的具体体现。
克隆它,破坏它,扩展它。该仓库证明了这不仅仅是图表——它是一个可部署、测试快速且能承受提供商更换的代码库。
结论
技术债务通常不会因一次糟糕的决策而突然到来——它是一次次框架捷径累积的结果,直到业务逻辑与交付机制如此纠缠,以至于任何一方改变都会导致另一方崩溃。六边形隔离是对此的直接结构性回答:将每个框架、数据库和供应商 SDK 推到边缘,并保留一个小型、无依赖的核心,它只知道它所服务的业务。
回报并非理论上的。它体现为测试以毫秒而非分钟运行,迁移花费一个冲刺而非一个季度,以及新工程师真正能够理解的代码库——因为业务规则没有埋藏在 ORM 钩子和路由处理器中。
停止像 15 分钟教程那样构建软件。隔离你的核心,保护你的边界,克隆 FlowBank 亲眼见证这一模式,并夺回你的工程预算。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.