解构 Coze Studio:DDD 与整洁架构的 Go 语言最佳实践
👋 大家好,我是十三!
Make Open Source Great Again! 字节在上个月将 AI IDE 平台 Trae Agent 项目开源后,在上周又将 AI Agent 平台 Coze Studio 开源了!作为主要使用 Golang 的服务端研发,对其后端代码产生了浓厚的兴趣,为此我花了一周的时间对其一探究竟。
在 AI Agent 与大模型应用蓬勃发展的今天,软件系统的复杂性与日俱增。在这样的背景下,一个清晰、健壮且易于演进的软件架构便是决定项目成败的基石。 DDD(领域驱动设计)与整洁架构(Clean Architecture)的组合,正是应对这种高度复杂性的强大武器。它们共同倡导“关注点分离”与“高内聚、低耦合”的核心理念,而 Coze Studio 正是基于 DDD 原则架构设计。
接下来我将探讨 Coze Studio 一个生产级的 AI Agent 平台是如何划分业务边界的?它的核心领域模型长什么样?以及一条用户请求是如何在清晰的层次间优雅地流转?
1. 宏观蓝图:整洁架构的分层艺术
整洁架构,由 Robert C. Martin 提出,其核心思想通过一个经典的“洋葱图”来体现。

Clean Architecture
其关键原则是依赖关系规则:源码依赖关系必须只指向内部,即层级越高的(越靠近业务核心),越不应该知道层级越低的(越靠近具体实现)的任何信息。
Coze Studio 的后端项目结构,完美地诠释了这一思想。
这四个核心目录,清晰地对应了整洁架构的四层模型:
**
domain
(领域层)**:系统的灵魂。它包含了企业级的业务规则和领域模型(实体、聚合、值对象、领域服务),是整个系统最核心、最稳定的部分。它不依赖于任何其他层。**
application
(应用层)**:编排领域对象以执行具体的应用场景(用例)。它依赖于领域层的接口,但不知道任何关于 UI 或数据库的实现细节。**
api
(接口层)**:作为应用的入口,负责适配和转换外部输入(如 HTTP 请求、RPC 调用)并传递给应用层。它依赖于应用层。**
infra
(基础设施层)**:提供所有与外部世界交互的具体实现,如数据库访问、文件系统、第三方服务客户端等。这一层实现了领域层和应用层定义的接口,是整个系统中最容易变化的部分。
这种分层使得业务逻辑与技术实现彻底解耦,带来了极高的可测试性、可维护性和灵活性。
2. 深入领域层:DDD 的战略与战术设计
DDD 的精髓在于将业务复杂性封装在领域模型中。Coze Studio 的 domain
目录为我们提供了一个绝佳的学习范本。
2.1 限界上下文 (Bounded Context)
DDD 的战略设计核心在于识别业务边界,划分出不同的“限界上下文”。在 Coze Studio 中,domain
目录下的子目录清晰地体现了这一点:
每一个子目录(如 conversation
, workflow
)都代表一个相对独立的业务领域。这种划分使得每个模块都可以高度内聚,拥有自己专属的术语和模型,极大地降低了整个系统的认知复杂度。
2.2 聚合、实体与仓储
让我们以核心的 conversation
限界上下文为例,深入其战术设计的细节。
实体 (Entity)
Conversation
实体的定义被放在一个 crossdomain
(跨域)的包中,说明它是一个被多个限界上下文共享的核心模型。
这个结构体非常“干净”,它只包含了数据属性。值得注意的是,它通过 AgentID
、CreatorID
等字段来关联其他聚合,但只保存其 ID,而非整个对象,这正是 DDD 的最佳实践,确保了聚合边界的清晰。
仓储接口 (Repository Interface)
领域模型如何被持久化?答案是通过仓储。领域层只定义仓储的接口,而不关心其实现。
这个 ConversationRepo
接口就是领域层与基础设施层之间的“契约”,它定义了所有对 Conversation
聚合的持久化操作。
领域服务 (Domain Service)
复杂的业务逻辑不适合放在实体中,而是应该由领域服务来承担。
应用层的代码将通过调用这个 Conversation
服务接口来执行业务逻辑,而无需关心其内部实现的复杂性。
“贫血”还是“充血”?Coze Studio 的选择与权衡
一个反常识的设计:Coze Studio 采用的是典型的“贫血领域模型”(Anemic Domain Model)——即 Conversation
实体本身只包含数据和 getter/setter,所有的业务逻辑都放在了领域服务 ConversationService
中。
这与经典的、推崇“充血领域模型”(Rich Domain Model)的 DDD 思想有所不同。“充血模型”强调数据和操作应该内聚在同一个实体对象中。
那么,Coze Studio 为何做出这样的选择呢?我觉得这是一种务实的权衡:
简单性和可测试性:贫血模型 + 领域服务的组合,逻辑清晰,结构简单。领域服务是无状态的,其依赖通过接口注入,非常容易进行单元测试。而充血模型中,业务逻辑分散在各个实体中,可能会导致更复杂的依赖关系和测试难度。
避免 ORM 复杂性:充血模型常常需要处理复杂的对象生命周期、懒加载、事务边界等问题,这会极大地增加 ORM 的使用难度。贫血模型则让数据持久化变得非常直接和可控。
因此,Coze Studio 的架构选择,是在遵循 DDD 核心思想(限界上下文、分层)的同时,根据其技术栈(Go)和工程化需求,做出的一种权衡。
3. 串联各层:一次请求的生命周期
让我们以“创建一次会话”为例,描绘一个请求在 Coze Studio 各层之间流转的完整路径:
接口层 (
api
):用户的 HTTP POST 请求
POST /v1/conversation/create
首先到达api/router/coze/api.go
中定义的路由。路由将请求分发给
api/handler/coze/
目录下的CreateConversation
处理函数。Handler 函数负责解析请求参数,然后调用应用层的服务。
应用层 (
application
):在
application/conversation/
目录下,有一个用例(UseCase)或应用服务。它接收到来自 Handler 的调用,然后调用领域服务的
Create
方法来执行核心业务逻辑。领域层 (
domain
):domain/conversation/conversation/service/conversation_impl.go
中的Create
方法被执行。它可能会进行一系列业务规则校验。
然后,它调用
ConversationRepo
接口的Create
方法,请求持久化新的Conversation
实体。基础设施层 (
infra
):infra
层提供了ConversationRepo
接口的具体实现,例如infra/impl/mysql/conversation_repo.go
(路径为推测)。这个实现类使用 GORM(从代码依赖中可知)将
Conversation
实体对象转换为数据库记录,并将其插入到 MySQL 表中。执行结果沿着调用链一路返回,最终由接口层的 Handler 封装成 HTTP 响应返回给用户。
这个流程清晰地展示了依赖倒置的威力:每一层都只依赖于其内部的接口,而不知道外部的具体实现,从而实现了完美的解耦。
4. 解耦的艺术:infra
中的 contract
与 impl
Coze Studio 在 infra
层以及其他很多地方,都采用了 contract
(契约,即接口)和 impl
(实现)分离的模式,这是将解耦思想贯彻到底的体现。
例如,领域层可能需要一个 ID 生成器,于是在 domain
层或 infra/contract
中会定义一个 IDGenerator
接口。而 infra/impl/idgen/
目录下则会提供一个基于某种算法(如雪花算法)的具体实现。
这种方式使得切换技术栈变得异常容易。如果未来决定将 ID 生成策略从雪花算法换成分布式的 UUID,只需要在 infra/impl
中提供一个新的实现,而上层的业务代码一行都不需要改动。
结论:架构是艺术品的地基
Coze Studio 的后端架构让我学到了一种新的 DDD 与整洁架构在 Go 语言中的具体落地方式,好的架构从来不是过度设计,而是对业务复杂性恰如其分的掌控与驾驭。我觉得 Code Studio 的源码无疑是一份极具价值的、可以反复学习的艺术工程。
👨💻 关于十三 Tech
资深服务端研发工程师,AI 编程实践者。专注分享真实的技术实践经验,相信 AI 是程序员的最佳搭档。希望能和大家一起写出更优雅的代码!
📧 联系方式:569893882@qq.com🌟 GitHub:@TriTechAI
版权声明: 本文为 InfoQ 作者【十三Tech】的原创文章。
原文链接:【http://xie.infoq.cn/article/12254c8a24bb9facfa4355990】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论