领域模型应隐藏struct字段并封装行为。所有字段小写,通过构造函数、只读方法和行为方法操作状态;业务逻辑内聚于实体或值对象;依赖抽象为接口;聚合间仅通过id关联;坚持高内聚以抵御重构惰性。

领域模型不该暴露 struct 字段给外部包
Go 没有类和访问修饰符,但高内聚要求领域对象的状态必须受控。直接导出字段(如 type User struct { Name string })会让外部随意读写,破坏不变量校验逻辑。
实操建议:
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
- 所有字段小写(非导出),仅通过导出的方法操作状态
- 构造函数返回指针(
NewUser(name string) (*User, error)),在内部做参数校验 - 读取用只读方法(
user.Name()),写入用行为方法(user.ChangeEmail(newEmail string) error),而非 setter - 避免为每个字段都配 Get/Set —— 只暴露业务语义明确的操作
领域行为必须封装在值对象或实体内部
常见错误是把业务逻辑散落在 service 层,比如 CalculateDiscount(order, user) 这种函数式写法。它绕过了领域对象的职责,导致模型退化为数据容器。
实操建议:
- 折扣规则属于
Order的一部分?那就让order.ApplyDiscount(policy DiscountPolicy) error自己处理 - 用户是否可下单?不应由外部判断
if user.Status == "active",而应提供user.CanPlaceOrder() bool - 跨实体逻辑(如“订单创建需检查库存”)放在聚合根(如
Order)里协调,而不是在 handler 或 service 中拼凑
避免在领域模型中引入基础设施依赖
领域层代码一旦依赖 *sql.DB、redis.Client 或 HTTP 客户端,就无法被单元测试隔离,也违背了分层原则。
实操建议:
- 用接口抽象协作方:定义
type UserRepository interface { FindByID(id string) (*User, error) } - 领域模型只持有接口,不关心实现;具体实现放在
infrastructure/包中 - 不要在
User方法里调用db.Exec—— 如果需要持久化副作用,应由上层(如 application service)触发 - 时间、随机数等外部依赖也需抽象(
Clock,RandomGenerator),否则测试时无法控制边界条件
聚合边界要清晰,避免跨聚合直接引用
一个典型反模式是 order.Customer.ID 这样直接访问另一个聚合根的字段。这会导致强耦合,且违反“聚合根是唯一入口”的约束。
实操建议:
- 聚合间只通过 ID 关联(
CustomerID string字段),不嵌套结构体或指针 - 需要客户姓名?要么在 Order 聚合内冗余必要字段(如
BillingName string),要么由上层查出后传入行为方法(order.Confirm(billingName string)) - 跨聚合校验(如“客户余额是否足够”)应在应用层完成,领域模型只负责接收已验证的数据
- 聚合根的工厂函数(
NewOrder(...))不应接受完整*Customer实例,只接受其 ID 和必要快照数据
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










