
在go语言ddd架构中,当不同业务模块(如people与apartments)需共享相同数据结构(如tenant复用people)时,应通过模型层解耦、组合而非继承、以及独立的关联服务包来实现高内聚低耦合的设计。
在go语言ddd架构中,当不同业务模块(如people与apartments)需共享相同数据结构(如tenant复用people)时,应通过模型层解耦、组合而非继承、以及独立的关联服务包来实现高内聚低耦合的设计。
在典型的Go DDD分层实践中,将领域模型、服务契约与实现逻辑混杂在单一业务包(如people/或apartments/)中,极易导致重复代码、循环依赖与维护困境。针对Tenant本质是Person在租赁上下文中的角色这一语义,核心原则是:复用结构,隔离语义;共享模型,分离行为。
✅ 推荐分层结构与职责划分
-
models/—— 纯数据契约层(无业务逻辑)
所有可复用的结构体统一定义于此,例如:// models/person.go package models type Person struct { ID uint64 `json:"id"` FirstName string `json:"first_name"` LastName string `json:"last_name"` Email string `json:"email"` } // models/tenant.go package models type Tenant struct { Person // 嵌入式组合,复用字段与方法 ApartmentID uint64 `json:"apartment_id"` LeaseStart time.Time `json:"lease_start"` IsActive bool `json:"is_active"` }⚠️ 注意:
Tenant不复制Person字段,而是通过嵌入(embedding)复用——既保持结构一致性,又支持扩展上下文专属字段(如ApartmentID、LeaseStart),且天然继承Person的方法(如FullName())。 -
interfaces/或各模块内contract/—— 清晰的服务契约
接口应按调用方需求定义,而非按实现方归属。PeopleService接口应置于people/contract/中,但Apartments模块若需查询租户,应定义自己的TenantReader接口(面向使用场景),再由适配器桥接:// apartments/contract/tenant_reader.go package contract import "yourapp/models" type TenantReader interface { GetTenantByID(id uint64) (*models.Tenant, error) ListTenantsByApartment(apartmentID uint64) ([]*models.Tenant, error) } // people/adapter/tenant_reader.go (适配器实现) func (s *PeopleService) GetTenantByID(id uint64) (*models.Tenant, error) { p, err := s.GetPersonByID(id) if err != nil { return nil, err } return &models.Tenant{Person: *p}, nil // 轻量转换,无字段冗余 } -
tenants/—— 领域关联服务包(关键解耦点)
当Apartment与Person存在强业务关联(如租约管理),应单独设立tenants/包,封装跨领域协作逻辑:- 定义
TenantService,协调PeopleRepository与ApartmentsRepository - 实现
AssignTenantToApartment()等复合操作,确保事务一致性 - 避免
apartments/直接依赖people/实现,仅依赖其接口与models/
- 定义
❌ 应避免的反模式
-
复制粘贴模型:在
apartments/中新建Tenant结构并手动同步Person字段 → 导致双写、不一致、难以维护。 -
跨包直接引用实现:
apartments/service.goimport"yourapp/people"并调用people.NewService()→ 违反依赖倒置,引发循环引用风险。 -
将接口放在调用方包中:如
apartments/定义PeopleService接口 → 混淆契约所有权,违背“提供者定义契约”原则。
总结
DDD在Go中的落地不是机械套用分层名称,而是围绕限界上下文(Bounded Context) 划分边界,并通过模型层下沉、接口前置声明、适配器桥接、关联服务升维四步实现优雅复用。Tenant不是Person的副本,而是其在租赁语境下的角色投影;真正的复用发生在models与interfaces层,而非实现细节。最终,每个业务包只依赖抽象契约与通用模型,彻底解除模块间硬耦合,为后续演进(如拆分为微服务)奠定坚实基础。


















