根据 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 替换为 MongoUserRepositoryInMemoryUserRepository(用于测试)无需修改核心。

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 等效实现。两者均不触及 AccountMoney 或用例——这正是本文论点的具体体现。

克隆它破坏它扩展它。该仓库证明了这不仅仅是图表——它是一个可部署、测试快速且能承受提供商更换的代码库。

结论

技术债务通常不会因一次糟糕的决策而突然到来——它是一次次框架捷径累积的结果,直到业务逻辑与交付机制如此纠缠,以至于任何一方改变都会导致另一方崩溃。六边形隔离是对此的直接结构性回答:将每个框架、数据库和供应商 SDK 推到边缘,并保留一个小型、无依赖的核心,它只知道它所服务的业务。

回报并非理论上的。它体现为测试以毫秒而非分钟运行,迁移花费一个冲刺而非一个季度,以及新工程师真正能够理解的代码库——因为业务规则没有埋藏在 ORM 钩子和路由处理器中。

停止像 15 分钟教程那样构建软件。隔离你的核心,保护你的边界,克隆 FlowBank 亲眼见证这一模式,并夺回你的工程预算。

#HappyCoding

#BuildScalableSystems