Go项目结构Go领域建模与架构设计 - 第2节
领域与子域:核心域、支撑域、通用域的定义与识别
在领域驱动设计(DDD)中,领域(Domain) 是组织要解决的业务问题空间,而 子域(Subdomain) 是对该问题空间的逻辑切分。正确识别子域类型,是后续划分限界上下文(Bounded Context)和设计微服务架构的前提。
1. 三种子域类型的定义
根据业务价值与战略重要性,子域分为三类:
| 类型 | 定义 | 业务特征 | 投入策略 |
|---|---|---|---|
| 核心域(Core Domain) | 组织最核心、最具竞争力的业务逻辑,是产品差异化的来源。 | 高复杂度、高价值、不可外包、需持续投入。 | 自研、集中最优资源、持续演进。 |
| 支撑域(Supporting Subdomain) | 支撑核心域运转,但本身不构成核心竞争力。 | 中等复杂度、业务必要、可部分复用。 | 自研或外包,追求效率与成本平衡。 |
| 通用域(Generic Subdomain) | 行业通用、无差异化价值的业务能力。 | 低复杂度、标准化、有成熟解决方案。 | 采购现成产品、使用开源方案、避免自研。 |
识别口诀: 核心域 = 赚钱的命脉;支撑域 = 必须做但不赚钱;通用域 = 别人做得更好更便宜。
2. 子域识别方法:三步法
- 业务事件风暴(Event Storming): 召集领域专家与开发团队,列出所有业务事件(如“订单已提交”“库存已扣减”),按业务流程分组。
- 价值-复杂度矩阵: 将每个业务事件/功能按“业务价值”和“业务复杂度”两个维度打分,落在高价值-高复杂度区域的即为核心域候选。
- 外部依赖分析: 检查每个子域是否依赖外部系统或第三方服务。若依赖成熟 SaaS/开源方案,则倾向归为通用域。
实战案例: 某电商平台中,“订单履约”涉及价格计算、库存锁定、支付对账,复杂度高且是收入来源 → 核心域;“商品图片存储”使用云对象存储 → 通用域;“客服工单系统”需定制化流程但非盈利核心 → 支撑域。
3. 子域与限界上下文的关系
每个子域通常对应一个或多个限界上下文(Bounded Context)。子域是问题空间的划分,限界上下文是解决方案空间的划分。下图展示了从业务子域到 Go 项目模块的映射关系:
Go 项目中的子域划分策略
在 Go 项目中,子域划分直接影响包结构、模块依赖和团队协作边界。以下是三种经过验证的划分策略:
策略一:按业务能力分层(推荐中型项目)
将项目按 internal/ 目录下的子域名称组织,每个子域内部再按 DDD 分层(handler / service / repository / domain)。
myproject/
├── cmd/
│ └── server/main.go
├── internal/
│ ├── order/ # 核心域:订单
│ │ ├── domain/ # 实体、值对象、领域服务
│ │ ├── service/ # 应用服务
│ │ ├── repository/ # 仓储接口与实现
│ │ └── handler/ # HTTP/gRPC 处理器
│ ├── pricing/ # 核心域:定价
│ ├── ticket/ # 支撑域:工单
│ ├── auth/ # 通用域:认证
│ └── audit/ # 通用域:审计
├── pkg/ # 可复用的通用工具
└── go.mod
优势: 每个子域独立演进,团队可并行开发;依赖关系清晰,核心域不依赖支撑域的实现细节。
策略二:按团队边界划分(推荐大型项目/多团队)
当多个团队负责不同子域时,每个子域可独立为一个 Go module,拥有独立的 go.mod 和版本管理。
# 仓库结构(monorepo 或多 repo)
order-service/ # 团队 A 负责核心域
├── cmd/
├── internal/
├── go.mod
└── go.sum
pricing-service/ # 团队 B 负责核心域
├── cmd/
├── internal/
├── go.mod
└── go.sum
auth-service/ # 团队 C 负责通用域(或使用开源方案)
├── cmd/
├── internal/
├── go.mod
└── go.sum
注意: 跨子域通信必须通过接口(interface)或事件(event),禁止直接导入另一个子域的 domain 包。Go 的循环依赖检测会在编译期强制这一规则。
策略三:渐进式划分(推荐从单体起步的项目)
项目初期将所有代码放在 internal/ 下,随着业务复杂度增加,逐步将核心域拆分为独立模块。下图展示了从单体到多模块的演进路径:
子域划分的常见误区
- 过度拆分: 将通用域(如日志、配置)也拆成独立微服务,导致运维成本剧增。通用域应优先使用库(library)而非服务(service)。
- 核心域模糊: 团队将所有子域一视同仁,平均分配资源,导致核心域创新速度被拖累。
- 忽略子域间的通信模式: 核心域与支撑域之间应使用领域事件(Domain Event)解耦,而非直接 RPC 调用。
Go 项目最佳实践: 在
internal/ 目录下,每个子域包对外只暴露接口(interface)和事件结构体,实现细节完全隐藏。这样即使后续将子域拆为独立服务,调用方代码也无需修改。