惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hugging Face - Blog
Hugging Face - Blog
云风的 BLOG
云风的 BLOG
大猫的无限游戏
大猫的无限游戏
M
MIT News - Artificial intelligence
L
LangChain Blog
阮一峰的网络日志
阮一峰的网络日志
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Recent Announcements
Recent Announcements
IT之家
IT之家
Google DeepMind News
Google DeepMind News
罗磊的独立博客
爱范儿
爱范儿
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
U
Unit 42
MongoDB | Blog
MongoDB | Blog
S
SegmentFault 最新的问题
B
Blog
博客园 - 叶小钗
月光博客
月光博客
Stack Overflow Blog
Stack Overflow Blog
V
Visual Studio Blog
C
Check Point Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 并发编程核心概念辨析 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解
还在被框架绑架?一文看懂“六边形架构”,让你的核心业务稳如...
Swizard · 2026-06-14 · via 博客园_首页

引言:那些年我们写过的“面条代码”

痛点场景: 你一定经历过这样的噩梦:系统最初用 MySQL 存储数据,后来为了性能要迁移到 MongoDB。结果你发现,业务代码里密密麻麻全是对 MySQL 驱动的直接调用。或者,老板突发奇想,要求把原本的 Web 页面功能,原封不动地搬到一个新的命令行工具(CLI)里,你却发现业务逻辑和 HTTP 的 Request / Response 对象死死绑定在一起。

“牵一发而动全身”,修改一行代码,整个系统崩溃。这是因为我们的核心业务逻辑被外部框架、数据库和 UI 强行“绑架”了。

解决方案: 这时候,六边形架构(Hexagonal Architecture) 闪亮登场。它的核心理念极其简单:将核心业务逻辑与外部依赖彻底隔离。它能让你的业务代码变得像插座一样通用,无论外接的是哪种数据库或哪个前端框架,核心系统都能从容应对。

概念拆解:餐厅后厨的生存哲学 (The "What" & "Why")

生活化类比:米其林餐厅的运作

别被“六边形”这个高大上的名字吓到,它其实也被称为端口与适配器模式(Ports and Adapters)。我们用“去餐厅点餐”来理解它:

想象一家顶级的米其林餐厅,它的核心竞争力是“后厨大厨的烹饪手艺”(核心业务逻辑)。

  1. 输入端(Driving):大厨根本不在乎客人是通过服务员点餐、通过美团外卖下单,还是打电话预定。大厨只认一样东西:标准化的点餐单(输入端口 Input Port)。服务员和外卖App在这里就是输入适配器(Input Adapter),负责把各种乱七八糟的请求翻译成大厨能看懂的点餐单。

  2. 输出端(Driven):大厨做菜需要土豆。他不会自己跑去菜市场买,他只会对采购员下达指令:“给我拿两个土豆”(输出端口 Output Port)。至于采购员是从门口超市买的,还是从远洋货轮上空运的(输出适配器 Output Adapter,如 MySQL、Redis、第三方 API),大厨毫不关心。

工作流图解:洋葱般的结构

如果画个图,六边形架构就像一个洋葱,分为内、中、外三层:

  • 最内层(Domain):纯粹的业务实体和规则(大厨的手艺)。这里没有任何外部框架的代码。

  • 中间层(Ports):接口定义层(点餐单和采购单)。它规定了外部如何与核心交互,以及核心如何向外部要数据。

  • 最外层(Adapters):具体的实现(服务员、外卖App、采购员)。比如 Spring Boot 控制器、REST API、MyBatis Mapper。

核心原则:依赖只能从外向内! 外层可以调用内层的接口,但内层绝对不能知道外层的任何细节。

动手实战:用 TypeScript 烤一个“六边形”MVP (The "How")

让我们用 TypeScript 写一个极简的“创建用户”功能,感受一下六边形架构的魅力。

1. 核心领域与端口(Domain & Ports)

首先,我们定义核心逻辑和它需要的“契约”。这部分代码绝对不能引入任何外部库(比如 expressmongoose)。

// 1. 领域模型 (Domain Model) - 纯纯的业务对象
export class User {
  constructor(public readonly id: string, public name: string, public email: string) {}
}

// 2. 输出端口 (Output Port) - 核心业务向外要数据的“采购单”
export interface UserRepositoryPort {
  save(user: User): Promise<void>;
  findByEmail(email: string): Promise<User | null>;
}

// 3. 输入端口 (Input Port) - 外部调用核心业务的“点餐单”
export interface CreateUserUseCase {
  execute(name: string, email: string): Promise<User>;
}

2. 应用服务(Application Service)

接下来,实现核心业务逻辑。它实现了输入端口,并调用输出端口。

// 4. 核心业务逻辑实现
export class UserService implements CreateUserUseCase {
  // 依赖注入:我不在乎你传给我的是 MySQL 还是 MongoDB,只要实现了接口就行!
  constructor(private readonly userRepository: UserRepositoryPort) {}

  async execute(name: string, email: string): Promise<User> {
    const existingUser = await this.userRepository.findByEmail(email);
    if (existingUser) {
      throw new Error("邮箱已被注册!"); // 纯粹的业务异常
    }
    
    const newUser = new User(Date.now().toString(), name, email);
    await this.userRepository.save(newUser); // 调用输出端口
    
    return newUser;
  }
}

3. 适配器层(Adapters)

最后,我们编写外围代码,连接真实的世界。

// 5. 输出适配器 (Output Adapter) - 真正连接数据库的地方
// 假设这里用的是内存数据库,随时可以换成 MongoUserRepository
export class InMemoryUserRepository implements UserRepositoryPort {
  private users: User[] = [];

  async save(user: User): Promise<void> {
    this.users.push(user);
    console.log(`[Database] 用户 ${user.name} 已保存到内存数据库。`);
  }

  async findByEmail(email: string): Promise<User | null> {
    return this.users.find(u => u.email === email) || null;
  }
}

// 6. 组装运行 (类似框架的 Controller / CLI 入口)
async function main() {
  // 装配阶段:将适配器插入端口
  const repository = new InMemoryUserRepository();
  const userService = new UserService(repository);

  // 模拟一个 HTTP 请求进来
  console.log("--> 收到前端请求:创建用户张三");
  try {
    const user = await userService.execute("张三", "zhangsan@example.com");
    console.log("<-- 响应前端:创建成功", user);
  } catch (error) {
    console.error("<-- 响应前端:创建失败", error.message);
  }
}

main();

代码解析: 为什么这么写?你会发现 UserService 里没有任何数据库的影子。如果明天老板说要把用户数据存到 Redis,你只需要新建一个 RedisUserRepository 实现 UserRepositoryPort 接口,然后在组装阶段替换掉 InMemoryUserRepository 即可。核心业务代码一行都不用改!

进阶深潜:新手防坑指南 (Deep Dive)

常见陷阱

⚠️ 在领域实体里写 ORM 注解 很多新手会在 User 类上加上 @Entity@Table(name="users")。千万别这么干!这就等于让大厨在菜谱上写满了采购员的进货路线。领域对象必须是纯洁的,数据库持久化对象(Entity/Model)应该在 Adapter 层单独定义,并在 Adapter 里进行转换映射(Mapper)。

最佳实践

  1. 测试驱动开发(TDD)的绝佳搭档:由于核心逻辑(Domain & Application)只依赖接口(Ports),你可以非常轻松地 Mock 掉数据库,实现秒级的单元测试。

  2. 切忌过度设计:六边形架构会增加类的数量(多了一堆接口和转换代码)。如果你的项目只是一个简单的、生命周期很短的 CRUD 脚本,传统的 MVC 三层架构可能更高效。好钢要用在刀刃(复杂核心业务)上。

总结与延伸 (Conclusion)

核心总结:六边形架构通过“端口与适配器”的机制,建立了一堵防火墙,让你的核心业务逻辑摆脱了底层技术选型的束缚,实现了真正的可插拔、可测试、可维护。