大家好,我是十三!欢迎来到十三Tech。
字节近期先后开源了 Trae Agent 和 Coze Studio 两大 AI Agent 平台。作为 Go 服务端研发,我把 Coze Studio 的后端读了一遍——它的工程结构几乎可以作为 DDD 与整洁架构在 Go 落地的范本:分层清晰、依赖倒置贯彻得彻底,没有为了"做架构"而过度设计。这篇文章就带你看一遍它的四层结构、领域模型组织方式,以及一次请求是如何在各层之间流转的。
1. 宏观蓝图:整洁架构的分层艺术
整洁架构的经典"洋葱图",本质就一句话:源码依赖只指向内部。越靠近业务核心的层,越不该知道任何关于数据库、HTTP、第三方 SDK 的细节。
Coze Studio 的后端目录把这层抽象直接铺成了物理结构:
coze-studio/backend/
├── api/ # 接口层 (Interface Adapters)
├── application/ # 应用层 (Application Business Rules)
├── domain/ # 领域层 (Enterprise Business Rules)
└── infra/ # 基础设施层 (Frameworks & Drivers)
四层各自的责任很克制:domain 是系统的灵魂,放业务规则和领域模型,不依赖任何其他层;application 编排领域对象执行用例,依赖领域接口但不碰 UI 或数据库;api 是入口,把外部输入(HTTP / RPC)翻译成应用层调用;infra 提供所有与外部世界交互的具体实现——它实现上层定义的接口,是整个系统最容易变化、最适合隔离的一层。这种切分让业务逻辑和技术实现彻底解耦,可测性和可演进性都来自这里。
2. 深入领域层:DDD 的战略与战术设计
2.1 限界上下文 (Bounded Context)
DDD 战术设计的第一步不是写实体,而是划边界。Coze Studio 的 domain 目录就老老实实做这件事:
domain/
├── agent/
├── conversation/
├── knowledge/
├── memory/
├── plugin/
├── user/
└── workflow/
...
每一个子目录是一个独立的业务领域,有自己的术语和模型。这种"先切上下文,再谈细节"的做法,把整套系统的认知复杂度降到了每个模块内部——你看 workflow 的时候不需要操心 memory 的概念。
2.2 聚合、实体与仓储
边界划好之后,每个上下文内部就是经典的 DDD 战术三件套:实体、仓储、领域服务。Coze Studio 的做法里藏着一个反常识的取舍。
Conversation 实体放在 crossdomain 包里——它被多个上下文共享,所以位置也透露了它的角色。结构体本身极其干净,只存数据:
// file: coze-studio/backend/api/model/crossdomain/conversation/conversation.go
type Conversation struct {
ID int64 `json:"id"`
SectionID int64 `json:"section_id"`
AgentID int64 `json:"agent_id"`
ConnectorID int64 `json:"connector_id"`
CreatorID int64 `json:"creator_id"`
Scene common.Scene `json:"scene"`
Status ConversationStatus `json:"status"`
Ext string `json:"ext"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
}
注意它只用 AgentID、CreatorID 这类 ID 字段关联其他聚合,不直接持有对象引用——聚合边界由此清晰。
仓储接口由领域层定义,实现交给基础设施层:
// file: coze-studio/backend/domain/conversation/conversation/repository/repository.go
type ConversationRepo interface {
Create(ctx context.Context, msg *entity.Conversation) (*entity.Conversation, error)
GetByID(ctx context.Context, id int64) (*entity.Conversation, error)
UpdateSection(ctx context.Context, id int64) (int64, error)
Delete(ctx context.Context, id int64) error
List(ctx context.Context, req *entity.ListMeta) ([]*entity.Conversation, bool, error)
}
复杂的业务逻辑沉到领域服务里:
// file: coze-studio/backend/domain/conversation/conversation/service/conversation.go
type Conversation interface {
Create(ctx context.Context, req *entity.CreateMeta) (*entity.Conversation, error)
GetByID(ctx context.Context, id int64) (*entity.Conversation, error)
NewConversationCtx(ctx context.Context, req *entity.NewConversationCtxRequest) (*entity.NewConversationCtxResponse, error)
Delete(ctx context.Context, id int64) error
List(ctx context.Context, req *entity.ListMeta) ([]*entity.Conversation, bool, error)
GetCurrentConversation(ctx context.Context, req *entity.GetCurrent) (*entity.Conversation, error)
}
应用层只调这个服务接口,内部细节一概不关心。
"贫血"还是"充血"?Coze Studio 的选择
Coze Studio 采用的是典型的贫血领域模型——实体只有数据和 getter/setter,业务逻辑全部放在领域服务里。这和经典 DDD 推崇的"充血模型"(数据和行为内聚在同一对象)相反。
我倾向于把它看作一种务实的工程权衡,而不是偏离。贫血模型 + 无状态领域服务,依赖通过接口注入,单元测试几乎零摩擦;充血模型在 Go 这种偏过程式的语言里,反而要处理对象生命周期、懒加载、事务边界,ORM 复杂度会被拉高一大截。Coze Studio 在 DDD 核心思想(限界上下文、分层、依赖倒置)上不松,在战术细节上向 Go 工程化妥协——这个取舍是干净的。
3. 串联各层:一次请求的生命周期
把"创建一次会话"这一请求放进四层里跑一遍,整个架构的运转方式就清楚了。
请求从 POST /v1/conversation/create 进入接口层,Handler 解析 HTTP 参数后调用应用层的 UseCase;UseCase 编排用例,调领域服务的 Create 方法;领域服务做业务校验,再调 ConversationRepo 接口请求持久化;接口的具体实现位于基础设施层,用 GORM 把实体写进 MySQL。结果沿调用链原路返回,最终由 Handler 封装成 HTTP 响应。
整套流程的精髓在依赖倒置:每一层只依赖自己定义的内部接口,对下层的具体实现一无所知。这就是为什么 Coze Studio 能在不改业务代码的前提下,自由替换数据库、缓存、ID 生成器——所有可变的实现都被压到了 infra 的最外圈。
4. 解耦的艺术:infra 中的 contract 与 impl
Coze Studio 在 infra 层把"契约"和"实现"物理分离,这是把依赖倒置贯彻到底的最后一招。
infra/
├── contract/ # 定义基础设施需要实现的接口
└── impl/ # 提供这些接口的具体技术实现
举个最常见的例子:领域层需要生成 ID,于是 contract 里定义 IDGenerator 接口,impl/idgen/ 下挂一个雪花算法实现。未来要换成 UUID,只需要在 impl 下新增一个实现,业务代码一行不动。这种"接口集中在 contract、实现集中在 impl"的物理布局,把"切换技术栈"变成了一个本地操作——没有跨层搜引用、没有改老代码的风险。
结论:架构是地基,不是装饰
读完 Coze Studio 的后端,最大的感受是:好的架构不是把所有模式都用上,而是在合适的地方做合适的取舍。它把 DDD 的限界上下文、整洁架构的依赖规则这些"难但正确"的事情做扎实了;同时在战术层——比如贫血模型、Go 风格的接口组织——做了务实的简化。这种判断力,比任何"教科书式 DDD"都值得学。
总结
本文从整洁架构的四层模型出发,剖析了 Coze Studio 的限界上下文划分、贫血领域模型的工程权衡、依赖倒置在请求生命周期里的体现,以及 contract/impl 把解耦贯彻到底的物理布局。这些做法对任何复杂业务系统都有参考价值——AI Agent 平台只是它的载体。
关于十三Tech
资深服务端研发工程师,AI 编程实践者。 专注分享真实的技术实践经验,相信 AI 是程序员的最佳搭档。 希望能和大家一起写出更优雅的代码!
联系方式:569893882@qq.com GitHub:@TriTechAI
