Go项目结构Go领域建模与架构设计 - 第2节

领域与子域:核心域、支撑域、通用域的定义与识别

在领域驱动设计(DDD)中,领域(Domain) 是组织要解决的业务问题空间,而 子域(Subdomain) 是对该问题空间的逻辑切分。正确识别子域类型,是后续划分限界上下文(Bounded Context)和设计微服务架构的前提。

1. 三种子域类型的定义

根据业务价值与战略重要性,子域分为三类:

类型 定义 业务特征 投入策略
核心域(Core Domain) 组织最核心、最具竞争力的业务逻辑,是产品差异化的来源。 高复杂度、高价值、不可外包、需持续投入。 自研、集中最优资源、持续演进。
支撑域(Supporting Subdomain) 支撑核心域运转,但本身不构成核心竞争力。 中等复杂度、业务必要、可部分复用。 自研或外包,追求效率与成本平衡。
通用域(Generic Subdomain) 行业通用、无差异化价值的业务能力。 低复杂度、标准化、有成熟解决方案。 采购现成产品、使用开源方案、避免自研。
识别口诀: 核心域 = 赚钱的命脉;支撑域 = 必须做但不赚钱;通用域 = 别人做得更好更便宜。

2. 子域识别方法:三步法

  1. 业务事件风暴(Event Storming): 召集领域专家与开发团队,列出所有业务事件(如“订单已提交”“库存已扣减”),按业务流程分组。
  2. 价值-复杂度矩阵: 将每个业务事件/功能按“业务价值”和“业务复杂度”两个维度打分,落在高价值-高复杂度区域的即为核心域候选。
  3. 外部依赖分析: 检查每个子域是否依赖外部系统或第三方服务。若依赖成熟 SaaS/开源方案,则倾向归为通用域。
实战案例: 某电商平台中,“订单履约”涉及价格计算、库存锁定、支付对账,复杂度高且是收入来源 → 核心域;“商品图片存储”使用云对象存储 → 通用域;“客服工单系统”需定制化流程但非盈利核心 → 支撑域。

3. 子域与限界上下文的关系

每个子域通常对应一个或多个限界上下文(Bounded Context)。子域是问题空间的划分,限界上下文是解决方案空间的划分。下图展示了从业务子域到 Go 项目模块的映射关系:

子域 → 限界上下文 → Go 项目模块映射 核心域 订单履约 / 定价引擎 库存分配策略 限界上下文 OrderContext / PricingContext Go 模块 internal/order/ internal/pricing/ 支撑域 客服工单 / 用户通知 后台审核流程 限界上下文 TicketContext / NotificationContext Go 模块 internal/ticket/ internal/notification/ 通用域 用户认证 / 日志审计 图片存储 / 短信发送 限界上下文 AuthContext / AuditContext Go 模块 internal/auth/ internal/audit/ 每个子域映射到 1 个或多个限界上下文,每个限界上下文对应 1 个 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/ 下,随着业务复杂度增加,逐步将核心域拆分为独立模块。下图展示了从单体到多模块的演进路径:

渐进式子域划分:从单体到多模块 阶段一:单体 internal/ ├── domain/ ├── service/ 阶段二:按子域分包 internal/ ├── order/ ├── auth/ 阶段三:独立模块 order-service/ auth-service/ (独立 go.mod) 演进原则 1. 先识别核心域,优先拆分 2. 支撑域和通用域保持单体,直到出现明确的独立部署需求

子域划分的常见误区

  • 过度拆分: 将通用域(如日志、配置)也拆成独立微服务,导致运维成本剧增。通用域应优先使用库(library)而非服务(service)。
  • 核心域模糊: 团队将所有子域一视同仁,平均分配资源,导致核心域创新速度被拖累。
  • 忽略子域间的通信模式: 核心域与支撑域之间应使用领域事件(Domain Event)解耦,而非直接 RPC 调用。
Go 项目最佳实践: 在 internal/ 目录下,每个子域包对外只暴露接口(interface)和事件结构体,实现细节完全隐藏。这样即使后续将子域拆为独立服务,调用方代码也无需修改。