单写者原则 = 聚合边界的工程实现
解决什么问题:分布式系统中同一聚合被多个服务写入导致冲突—— 单写者原则强制"一个聚合 = 一个写者",让冲突从源头上消失。
业务问题
真实场景
场景 1:电商订单被多个服务修改
python
# 反模式:多个微服务修改同一聚合
class OrderService:
def update_status(self, order_id, status):
# 订单服务写
...
class ShippingService:
def mark_shipped(self, order_id):
# 物流服务也写同一订单
...
class PaymentService:
def mark_paid(self, order_id):
# 支付服务也写同一订单
...
# 问题:
# - 三个服务都能写 Order 聚合 → 分布式写冲突
# - 谁先谁后?最后写赢?
# - 不变量(订单状态机)谁保证?Bellemare Ch 2 原话:
"Microservice Single Writer Principle... One microservice is solely responsible for each aggregate... Other services must consume and react, not directly modify."
失败模式
| 失败模式 | 后果 |
|---|---|
| 多服务写同一聚合 | 分布式冲突;状态不一致 |
| 应用层双写 | DB 和事件流不一致(无事务保证) |
| 共享数据库写 | 紧耦合;破坏 BC 边界 |
控制论视角
Ch 6 §6.3:"解耦" + Ch 4 §4.6:"不变量必须被维护"
单写者 = 控制律的唯一执行者
- 一个聚合 = 一个控制律 = 一个执行者
- 不变量 = 受控状态的稳定条件(只有一个执行者能维护)
- 状态反馈 = 只有执行者能接收并响应对位:
- 写者 = 控制律的执行器(受控对象的唯一"手")
- 消费者 = 观测器(只能看,不能改)
- 事件流 = 反馈通道(观测结果通过事件流传递)
核心概念
单写者原则
原则:
一个聚合类型 = 一个微服务 = 唯一写者
约束:
- 只有该微服务可以生产该聚合的事件
- 其他微服务只能消费,不能直接修改
- 该微服务持有该聚合的所有状态变更权单写者 vs 多写者
| 维度 | 单写者 | 多写者 |
|---|---|---|
| 冲突 | 无 | 高 |
| 一致性 | 简单 | 复杂(需 Paxos/Raft) |
| 性能 | 高(无协调) | 低(协调开销) |
| 可调试 | 易 | 难 |
| 适用 | 90% 场景 | 极少数(共享数据) |
单写者的实施路径
步骤 1:识别聚合(按业务边界)
- 一个 BC 一个聚合根
- 聚合根包含业务不变量
步骤 2:指派写者
- 一个微服务拥有该聚合
- 该服务是"真理之源"
步骤 3:限制写权限
- 其他服务消费事件
- 通过事件流间接影响
- 不能直接调 API 修改
步骤 4:处理读
- 其他服务需要读 → 通过事件流物化
- 或通过专门的查询 API与 IDDD 聚合的关系
IDDD Ch 10 聚合 4 规则:
- 小聚合(性能)
- 一致性边界(事务)
- ID 引用(解耦)
- 边界外最终一致
单写者原则 = 聚合的"运行时实现"
- 聚合 = 静态边界
- 单写者 = 动态执行者
- 二者结合 = 边界清晰 + 责任清晰工程实践
决策 1:识别聚合边界
决策树:
多个服务要改同一数据?
- 是 → 必然违反单写者
- 否 → 考虑合并服务或拆分聚合
数据有强不变量?
- 是 → 必须聚合 + 单写者
- 否 → 可以独立写决策 2:处理跨聚合修改
问题:物流服务要修改订单状态
反模式:
- 物流服务直接调订单 API 修改 → 违反单写者
正模式:
- 物流服务发 OrderShippedEvent 事件
- 订单服务订阅事件,更新订单状态
- 订单服务是订单聚合的唯一写者反模式 vs 正模式
| 反模式 | 正模式 |
|---|---|
| 多服务直接写同一 DB | 单服务拥有聚合 |
| 共享数据库(multi-writer) | 单写者 + 事件流 |
| 同步调用修改数据 | 事件流传递状态变更 |
| 应用层双写(DB + Event) | CDC 单写(DB 是 SSOT) |
与 CDC 配合
模式:单写者 + CDC
1. 服务 A 写自己的 DB(单写者)
2. CDC 监听 DB 的 binlog
3. binlog 转换为事件流
4. 其他服务消费事件
优点:
- 单写者原则不破坏(DB 写入)
- 事件流自动生成(无应用双写)
- 完美解耦性能指标
| 指标 | 衡量 | 健康值 |
|---|---|---|
| 单写者覆盖率 | 多少聚合有唯一写者 | > 95% |
| 跨服务直接写 | 多少写操作跨服务 | 0 |
| 冲突率 | 写冲突 / 总写 | 0 |
| CDC 延迟 | DB 变更 → 事件发出 | < 1s |
边界判断
何时用单写者(★ 必须)
- 有清晰聚合边界
- 业务有不变量
- 多个服务可能涉及同一数据
何时不用(极少数)
- 真正的共享数据(如用户偏好)
- 多主复制(multi-master)刻意设计
- 单服务内的多线程(不是微服务问题)
何时升级
| 当前 | 升级到 | 触发条件 |
|---|---|---|
| 应用双写 | 单写者 + CDC | 频繁不一致 |
| 多服务共享 DB | 单写者 + 事件流 | BC 边界确立 |
| 同步调用改数据 | 事件流 | 性能/解耦需求 |
关键洞察
- 单写者 = 聚合的运行时形态——静态边界(DDD 聚合)+ 动态执行(单写者)= 完整一致性。
- 写者唯一 = 冲突从源头消失——比事后协调(分布式锁/事务)简单 100 倍。
- 单写者 + CDC = 完美解耦——DB 是 SSOT,事件流是投影,应用不双写。
- 与 IDDD 同构——Ch 10 聚合 4 规则的"小聚合"前提就是单写者。
- 与 cybernetics 同构——单写者 = 受控对象的唯一执行器 = Ch 4 §4.6 反馈环的唯一接收方。