引言:为什么你的Go项目需要DDD?
\n\n你是否曾经用Go编写过一个服务,开始时只有几个端点和三张数据库表,但半年后却变成了一个怪物,拥有长达数千行的handler.go文件和散落在代码各处的魔法数字?很熟悉吧。我也经历过。
经典Go开发的问题在于——我们过早地考虑数据库和JSON,却忘记了最重要的事情:业务逻辑。领域驱动设计(DDD)正是在你想要从混乱中理出头绪时提供帮助。但如何将DDD落地到Go中呢?毕竟这门语言并没有给我们提供类和继承的便利。让我们一起来探讨。
\n\n在本文中,我将展示如何在Go项目中应用领域驱动设计,不过度狂热,并提供可运行的代码示例和清晰的结构。你将学会如何区分主次,编写出不仅机器能理解,业务分析师也能看懂的代码。
\n\n什么是DDD,为什么Go开发者需要它?
\n\n领域驱动设计——不是关于技术。而是关于语言。是关于让代码用业务的语言说话。如果你的业务领域是电商网站,代码中应该有Order、Product、Cart,而不是DataTransferObject和EntityManager。
Go以其简洁性和无魔法的特性,非常适合DDD。这里没有代理类和反射来掩盖本质。在Go中,你不得不保持诚实:如果你有一个聚合,那就是一个明确的结构体;如果你有一个仓储,那就是一个接口。
\n\n我们将讨论的DDD主要构建块:
\n- \n
- 实体(Entity)——具有唯一标识符的对象(例如
User)。 \n - 值对象(Value Object)——描述特征的不可变对象(例如
Address)。 \n - 聚合(Aggregate)——保证一致性的实体集群(例如包含
OrderItem的Order)。 \n - 仓储(Repository)——隐藏存储细节的对象集合。 \n
- 领域服务(Domain Service)——无法放入单个实体的操作。 \n
现在,让我们将其翻译成Go代码。
\n\n项目结构:如何在文件夹中不迷失方向
\n\n好消息:Go不强制任何结构。坏消息:没有结构,你会很快迷失。对于Go中的DDD,我推荐以下代码组织方式:
\n\nyour-project/\n├── cmd/\n│ └── server/\n│ └── main.go\n├── internal/\n│ ├── domain/ # 纯净核心:实体、规则\n│ │ ├── order.go\n│ │ ├── product.go\n│ │ └── errors.go\n│ ├── application/ # 用例/应用服务\n│ │ ├── order_service.go\n│ │ └── product_service.go\n│ └── infrastructure/ # 数据库、外部API、HTTP处理器\n│ ├── postgres/\n│ │ └── order_repository.go\n│ └── http/\n│ └── handler.go\n└── go.mod\n\n请注意:domain不导入infrastructure。这是DDD的黄金法则——领域层对数据库和HTTP一无所知。它生活在纯净的结构体和接口的真空中。
编写领域层:Order和值对象
\n\n我们从核心开始。假设我们正在构建一个订单管理系统。以下是Order实体的可能样子:
// internal/domain/order.go\npackage domain\n\nimport (\n "errors"\n "time"\n)\n\ntype OrderStatus string\n\nconst (\n OrderStatusPending OrderStatus = "pending"\n OrderStatusConfirmed OrderStatus = "confirmed"\n OrderStatusCancelled OrderStatus = "cancelled"\n)\n\ntype OrderID string\n\ntype Order struct {\n ID OrderID\n CustomerID string\n Items []OrderItem\n Status OrderStatus\n CreatedAt time.Time\n Upd