一个用例四个入口:domain-driven-hexagon同命令支持HTTP、CLI、消息与GraphQL
【免费下载链接】domain-driven-hexagonLearn Domain-Driven Design, software architecture, design patterns, best practices. Code examples included项目地址: https://gitcode.com/gh_mirrors/do/domain-driven-hexagon
在 domain-driven-hexagon(领域驱动六边形架构)开源项目中,"创建一个用户"这同一个业务用例,竟然同时支持 HTTP 接口、命令行、消息队列和 GraphQL 四种调用方式,而且业务逻辑只写一遍。这就是六边形架构 + CQRS 的魅力:命令(Command)是唯一入口,四种调用方式都只是"接口适配器"。本文带你快速看懂这一设计,适合刚接触 DDD 和后端架构的新手。
为什么同一个命令能有多个入口?
传统写法是"每个接口一套逻辑":HTTP 控制器里写一遍、消息监听里再写一遍,很快就会出现重复代码和不一致行为。
domain-driven-hexagon 的思路是:把业务动作抽象为命令对象,所有入口只负责"把参数变成命令",剩下的全部交给命令处理器。架构图如下,左侧的 HTTP、CLI、消息队列等入口都通过 DTO 汇入核心层:
核心关键词就藏在图中:Interface Adapters(接口适配器)负责各种入口,Application Core(应用核心)只包含命令处理器与领域实体。
四个入口,四种"壳"
"创建用户"用例位于 src/modules/user/commands/create-user/ 目录下,一眼就能看全四种入口:
| 入口 | 文件 | 适用场景 |
|---|---|---|
| HTTP | create-user.http.controller.ts | 浏览器/移动端 API 调用 |
| CLI | create-user.cli.controller.ts | 运维脚本、定时任务 |
| 消息队列 | create-user.message.controller.ts | 微服务间异步解耦 |
| GraphQL | create-user.graphql-resolver.ts | 前端按需取数的 BFF 层 |
每个入口的代码结构惊人地一致,可以概括为三步走:
- 解析参数:HTTP 从
@Body()取,CLI 从位置参数取,消息从user.create事件取,GraphQL 从@Args('input')取; - 构造命令:都执行
new CreateUserCommand(参数); - 投递执行:都调用
commandBus.execute(command)。
以消息入口为例,create-user.message.controller.ts 通过@MessagePattern('user.create')订阅微服务消息,收到消息后仅仅把它转成CreateUserCommand交给 CommandBus,没有任何业务逻辑。CLI 入口 create-user.cli.controller.ts 则注册了new user <email> <country> <postalCode> <street>这样的控制台命令,方便在终端直接创建用户。
💡 入口之间的差异(参数校验、错误码、响应格式)都留在了"壳"里,互不干扰。比如 HTTP 入口在捕获
UserAlreadyExistsError时返回 409 Conflict,见 create-user.http.controller.ts 中的match分支。
命令与处理器:逻辑只有一份
命令对象定义在 create-user.command.ts 中,只包含 email、country、postalCode、street 四个纯数据字段,不含任何处理逻辑。
真正干活的是命令处理器 create-user.service.ts:
- 用命令参数构建
UserEntity聚合根与Address值对象; - 在事务内插入用户,保证领域事件原子性地被消费;
- 成功返回
Ok(用户ID),重复则返回Err(UserAlreadyExistsError)。
这种Result类型(而非抛异常)的返回值,让四个入口都能用同一套方式判断成功或失败。参数校验则由请求 DTO 统一承担,如 create-user.request.dto.ts 用class-validator约束邮箱格式与长度。
四种入口、命令、处理器、模块注册的关联关系,在 user.module.ts 中一目了然:httpControllers、messageControllers、cliControllers、graphqlResolvers分组注册,共享同一批commandHandlers。
隐藏彩蛋:领域事件联动钱包 🎁
通过任意一个入口创建用户后,系统都会发布UserCreatedDomainEvent领域事件。钱包模块的事件处理器 create-wallet-when-user-is-created.domain-event-handler.ts 监听到事件后自动为用户创建钱包。
这意味着:无论用户是走 HTTP、CLI、消息还是 GraphQL 进入系统,"自动开户"的行为都 100% 一致——因为触发点不在入口,而在领域事件。这正是六边形架构"依赖倒置"的直观收益。
如何跑起来体验一下
git clone https://gitcode.com/gh_mirrors/do/domain-driven-hexagon cd domain-driven-hexagon npm install npm run start:dev启动后访问http://localhost:3000/docs查看 Swagger 文档,尝试POST /api/v1/user;也可以使用项目内置 Docker 环境 docker/docker-compose.yml 一键拉起依赖。
小结:这套模式能迁移到你自己的项目吗?
✅ 适用:同一业务动作有多个触发来源(API、定时任务、第三方回调、异步消息)时;
✅ 适用:希望把"入口协议"与"业务规则"彻底解耦,方便分别测试与替换时;
⚠️ 注意:小项目可能用不上全部四层,作者也在 README 中提醒——以上建议是推荐而非教条,可按需裁剪。
一句话总结:让命令成为唯一事实来源,让 HTTP、CLI、消息、GraphQL 都只是可插拔的适配器——这就是 domain-driven-hexagon 教给我们的、最实用的六边形架构实践。
【免费下载链接】domain-driven-hexagonLearn Domain-Driven Design, software architecture, design patterns, best practices. Code examples included项目地址: https://gitcode.com/gh_mirrors/do/domain-driven-hexagon
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考