领域驱动设计基础:领域、子域、限界上下文、实体与值对象、领域事件
说实话,很多数据产品经理第一次接触 DDD 时,都会被这些术语吓到。我当年也一样。记得 2017 年我第一次参与一个电商中台项目,技术架构师张口就是「限界上下文」、「聚合根」,我坐在会议室里一脸懵。
后来我才明白,DDD 不是给代码加一层包装,而是帮我们理清业务到底在做什么。你想想看,如果连业务边界都画不清楚,数据产品怎么可能做对?
核心认知:领域驱动设计不是技术架构,而是一种思维方式。它强迫你先想清楚「业务是什么」,再想「系统怎么做」。
领域与子域:你的数据产品到底在解决什么问题?
领域,说白了就是你要解决的业务范围。比如你做的是电商领域,还是医疗领域,还是金融领域?每个领域都有自己独特的规则和逻辑。
但一个领域太大了,你不可能一口吃成胖子。所以我们需要拆分子域。
- 核心子域:你的核心竞争力。比如电商的订单系统、支付系统。做不好这个,公司就活不下去。
- 支撑子域:辅助核心业务,但也很重要。比如商品推荐、用户评价。
- 通用子域:所有公司都差不多,直接买现成的。比如邮件通知、短信服务。
我在项目中遇到过最典型的错误,就是团队把大量精力花在通用子域上,自己写了一套邮件系统,结果核心订单模块一塌糊涂。嗯,这其实是个很常见的坑。
我的建议:做数据产品规划时,先花一周时间画出你所在领域的子域地图。把核心子域标红,支撑子域标黄,通用子域标绿。然后问自己:我的数据产品到底在服务哪个子域?
限界上下文:数据产品的边界在哪里?
限界上下文这个概念,我理解了很久才真正搞懂。说白了,它就是「一个业务概念的边界」。同一个词,在不同上下文里意思完全不一样。
举个例子,「用户」这个词:
| 上下文 | 「用户」的含义 | 关键属性 |
|---|---|---|
| 订单上下文 | 下单的人 | 用户ID、收货地址、支付方式 |
| 营销上下文 | 被触达的对象 | 用户ID、手机号、标签、渠道 |
| 客服上下文 | 需要服务的客户 | 用户ID、历史工单、满意度 |
你看,同一个「用户」,在不同上下文里关注的点完全不同。如果你强行用一个「大用户表」去覆盖所有场景,最后一定会变成谁也看不懂的怪物。
我曾经接手过一个数据平台,里面有个「客户宽表」,300 多个字段。业务方说查个东西要等 5 分钟,开发说不敢改因为不知道谁在用。这就是典型的限界上下文没划清楚。
避坑指南:我曾经见过一个团队,把订单数据和用户行为数据强行放在同一个数据模型中,结果订单字段和事件字段互相污染,查询性能一塌糊涂。记住:不同上下文的数据,就该分开建模。
实体与值对象:数据建模的基本功
这两个概念,是 DDD 里最基础但也最容易搞混的。我直接说人话:
- 实体:有唯一标识,会变化。比如一个订单,订单号不变,但状态从「待支付」变成「已支付」。
- 值对象:没有唯一标识,只看值。比如地址「北京市朝阳区 xxx」,换一个地址就是另一个对象。
为什么这个区分很重要?因为数据产品里,你设计的表结构、API 接口、数据模型,都依赖这个判断。
实战经验:我设计数据模型时,会先问自己三个问题:
- 这个对象需要被单独追踪吗?(需要 → 实体)
- 它的值变了,我需要记录历史吗?(需要 → 实体)
- 它只是描述另一个对象的属性吗?(是 → 值对象)
举个具体的例子。你在设计一个用户画像系统:
// 实体:用户
class User {
userId: string; // 唯一标识
name: string;
level: string; // 会变化
lastLoginTime: Date;
}
// 值对象:地址
class Address {
province: string;
city: string;
district: string;
detail: string;
// 没有 ID,两个 Address 只要值一样就相等
}
你看,用户是实体,因为我们需要追踪他的等级变化。地址是值对象,换地址了直接替换就行,不需要知道「旧地址去哪了」。
领域事件:数据产品里的「发生了什么」
领域事件,就是业务中发生的、值得记录的事情。比如「订单已支付」、「用户已注册」、「商品已上架」。
为什么数据产品要关注领域事件?因为数据产品本质上就是在消费这些事件。你做的实时大屏、用户行为分析、异常监控,底层都是领域事件在驱动。
我建议你在设计数据产品时,先梳理出核心的领域事件清单:
| 事件名称 | 触发时机 | 关键数据 | 消费方 |
|---|---|---|---|
| OrderPlaced | 用户提交订单 | 订单ID、金额、商品列表 | 实时大屏、推荐系统 |
| PaymentCompleted | 支付成功 | 订单ID、支付方式、金额 | 财务系统、用户积分 |
| UserRegistered | 新用户注册 | 用户ID、注册渠道、时间 | 用户画像、营销系统 |
我的习惯:每个领域事件都包含三个要素:事件名称(过去时)、事件发生时间、事件携带的数据。不要往事件里塞太多东西,够用就行。我曾经见过一个事件带了 50 个字段,最后没人知道哪些是真正有用的。
一张图看懂 DDD 核心概念
下面这张图,是我自己总结的 DDD 核心概念关系图。每次给团队培训我都会画一遍,你感受一下:
从这张图你可以看到:领域包含多个子域,每个子域有自己的限界上下文,上下文里定义实体和值对象,而领域事件则贯穿整个流程,记录业务的变化。
总结一下
DDD 的基础概念,说白了就是帮你回答几个问题:
- 你的业务范围是什么?(领域)
- 核心在哪,辅助在哪,通用的在哪?(子域)
- 每个模块的边界在哪,数据模型怎么独立?(限界上下文)
- 哪些东西需要追踪变化,哪些只是描述?(实体 vs 值对象)
- 业务中发生了什么值得记录的事?(领域事件)