Go 项目怎么组织:DDD 4 层 vs MVC vs 脚本式

Go 项目怎么组织:DDD 4 层 vs MVC vs 脚本式

系列「企业级 AI Agent 实现拆解」E32 篇,Part 9 起步篇第二章。上一篇5 分钟跑通你的第一个 AI Agent把 Agent 跑起来了,代码全在一个 main.go 里。这篇讲什么时候需要分层、怎么分。

读完这篇你会知道

  • 脚本式单文件:什么时候够用,什么时候开始痛
  • MVC 三层:Web 项目为什么能用,Agent 项目为什么不够用
  • DDD 4 层:怎么分、每层负责什么、边界在哪里
  • 实用建议:小项目从脚本起步,何时该升级结构

从你现在的代码开始

上一篇 的成果是这个:

go 复制代码
my-first-agent/
├── go.mod
└── main.go   ← 所有东西都在这里

能跑,够清晰。这就是脚本式------没有层次,一文件到底。


阶段一:脚本式(1-3 个文件)

适合: demo、原型验证、学习用途

go 复制代码
my-agent/
├── go.mod
├── main.go      ← Agent 组装 + 问答循环
├── tools.go     ← 所有工具函数
└── prompts.go   ← 系统提示字符串

当你的 Agent 功能简单、不需要测试、不需要多人协作时,这个结构是对的。不要过早分层。

脚本式开始痛的信号:

  • "我想换 DeepSeek 为 Claude,但模型初始化散在 3 个地方"
  • "我想给工具加单元测试,但工具直接依赖 HTTP client,没法 mock"
  • "同事接手这个项目,看不懂 main.go 里的 500 行"

阶段二:MVC 三层(Web 项目的默认选择)

很多 Go 开发者写过 Web 服务,会自然想套 MVC:

perl 复制代码
my-agent/
├── controller/   ← HTTP handler,接收请求
├── service/      ← 业务逻辑
└── repository/   ← 数据库操作

MVC 的核心思路是:请求处理 / 业务逻辑 / 数据存储 分开

对 Agent 项目,MVC 能解决一半问题。 你可以把 Agent 组装放在 service/,HTTP 接口放在 controller/。但很快会遇到问题:

问题 1:模型和工具应该放哪?

service/agent.go 里直接 import openai,当你想换 DeepSeek 时,需要改这个文件。当你想测试 service/ 里的业务逻辑时,必须有一个真实的 LLM 网络连接。

问题 2:领域概念的归属不清晰。

Session(对话会话)、AgentConfig(Agent 配置)、Tool(工具)------这些"是什么"的概念,应该跟"怎么存"和"怎么传输"的代码彻底分开。MVC 没有给"概念层"留位置。


阶段三:DDD 4 层(复杂 Agent 的合理选择)

DDD(领域驱动设计)把代码分成 4 层,从内到外依赖方向是单向的:

markdown 复制代码
domain/          ← 业务概念:Session、Message、AgentConfig
    └── 不依赖任何外部库,纯 Go 结构体 + 接口

application/     ← 用例:RunTurn、ResumeInterrupt、GetSession
    └── 只依赖 domain + port 接口,不知道 HTTP/gRPC/数据库

infrastructure/  ← 技术实现:数据库、LLM client、SSE 推流
    └── 实现 domain 里定义的接口

interfaces/      ← 协议适配:HTTP handler、gRPC server
    └── 只调 application 用例,不写业务逻辑

依赖方向永远是:interfacesapplicationdomaininfrastructure 实现 domain 定义的接口(依赖倒置)。


具体例子:换 LLM 只改一个文件

脚本式(痛点)

go 复制代码
// main.go
cm, _ := openai.NewChatModel(ctx, &openai.ChatModelConfig{
    BaseURL: "https://api.deepseek.com",
    Model:   "deepseek-chat",
    APIKey:  os.Getenv("DEEPSEEK_API_KEY"),
})
// 想换 Claude?搜遍整个项目

DDD 4 层(怎么处理)

domain 层(定义接口,不知道有哪些模型):

go 复制代码
// domain/port/ports.go
type LLMClient interface {
    Chat(ctx context.Context, messages []Message) (string, error)
    ChatStream(ctx context.Context, messages []Message) (io.Reader, error)
}

infrastructure 层(具体实现,每个模型一个文件):

go 复制代码
// infrastructure/llmclient/deepseek.go
type DeepSeekClient struct { ... }
func (c *DeepSeekClient) Chat(...) { ... }   // 实现 LLMClient 接口

// infrastructure/llmclient/claude.go
type ClaudeClient struct { ... }
func (c *ClaudeClient) Chat(...) { ... }

application 层(用例,只知道 LLMClient 接口):

go 复制代码
// application/command/run_turn.go
type RunTurnHandler struct {
    llm port.LLMClient  // 接口,不是具体类型
}
func (h *RunTurnHandler) Handle(ctx context.Context, ...) {
    // 调 h.llm.Chat(...),不知道是 DeepSeek 还是 Claude
}

换模型只改 main.go 里的 wire(依赖注入):

go 复制代码
// 换前
handler := &RunTurnHandler{llm: deepseek.New(...)}
// 换后
handler := &RunTurnHandler{llm: claude.New(...)}

application/ 的代码一行不动,单元测试也不需要修改。


什么时候真的需要 DDD 4 层?

诚实说:大多数个人项目和小团队用不上完整的 DDD

DDD 的价值体现在:

场景 DDD 的帮助
需要单元测试 Agent 逻辑 domain/application 层零外部依赖,mock 接口就够
多人协作,边界要清晰 每层职责明确,不会互相踩
需要频繁换 LLM 或工具实现 infrastructure 层可以独立替换
同一套业务逻辑要支持 REST + gRPC application 层不感知协议
项目规模 > 3 个 BC、10+ 开发者 强制分层防止代码腐化

如果你的项目是: 1 个人、1 个 Agent 功能、不需要多协议、代码量 < 2000 行------用脚本式或者简单的 service 包分拆就够了。


实用的渐进路径

go 复制代码
第 1 周:单文件 main.go(上一篇 的方式)
         ↓ 痛点出现(测试难/换模型烦)时
第 2 阶段:按职责拆文件
    main.go         ← 组装 + 入口
    agent.go        ← Agent 逻辑
    tools.go        ← 工具定义
    model.go        ← ChatModel 初始化
         ↓ 多人协作 / 多协议 / 持续迭代时
第 3 阶段:DDD 4 层
    domain/         ← 概念
    application/    ← 用例
    infrastructure/ ← 技术
    interfaces/     ← 协议

不要在第 1 周就上 DDD。 在你还不清楚 Agent 业务的边界时,提前分层只会让你在"这个函数该放哪里"上浪费时间。


DeepFlux 的选择

DeepFlux 是一个要支持多租户、多 LLM、多协议(REST + gRPC)、HITL 中断、可观测性的企业级平台,10 个限界上下文,需要长期维护。

它用了完整的 DDD 4 层,并且强制规则(用 go-arch-lint 在 CI 检查分层边界)。

这是合理的------但它的场景比大多数 Agent 项目复杂得多。

匹配项目规模选结构,不要因为"大公司用 DDD"就给自己 100 行的脚本上 DDD。


小结

结构 适用场景 痛点
脚本式 学习、demo、原型 规模稍大就混乱
MVC Web 服务背景、中等规模 领域概念归属模糊
DDD 4 层 多人、多协议、长期维护 前期分层开销高

下一篇讲 Prompt 模板管理:怎么从硬编码字符串升级到可版本化、可测试的模板。


参考架构:DeepFlux platform (暂未开源) · server/internal/agent/ · docs/architecture/ddd-bounded-contexts.md

相关推荐
HjhIron1 小时前
从一问一答到自主干活:Agent 的核心设计模式 ReAct 是如何工作的
ai编程
码农胖大海1 小时前
AI 辅助学习的正确姿势和方法
人工智能·程序员·ai编程
GISer_Jing1 小时前
一套H5跑通三端:App+小程序内嵌H5跨端适配全栈解决方案
前端·前端框架·ai编程
maynormoe2 小时前
从单轮 Prompt 到 Agent:三种构建范式的工程复盘
agent
老梁agent2 小时前
Agent 可观测性实战:全链路追踪 + JSON 结构化日志
物联网·agent
web_Leon2 小时前
Codex一键复刻任何网站:从安装到实战,30秒搞定原来两天的活
人工智能·ai编程
想ai抽2 小时前
Hermes Agent vs Loop Agent 技术调研
agent·loop·harness
谢文峰2 小时前
Claude Code 的诞生史:一个 AI 编程工具如何从内部 CLI 长成 Agent
ai编程
用户2930750976692 小时前
手写一个 AI 编程助手:Tool Calling 与 ReAct Agent 全解
agent