所有权、身份与不变量:谁有权说"是真的"
订单、支付、搜索都说自己有"订单状态"时,最终值取决于时序而不是业务。本页定义权威写入边界、区分四种身份,并说明拆库如何改变业务保证。分片与复制如何落实这些边界见分区、复制和存储布局;跨资源事务的协议细节由业务幂等与跨资源事务权威讲授,本页不重复。
权威边界与四种身份
每个业务事实应有一个权威写入边界。若多个服务都能更新同一事实,需要显式冲突协议或单一裁决者,否则最终值取决于时序。主键是数据库行的物理/逻辑身份,业务键是领域唯一性,事件 ID 是一次事实的稳定身份,幂等键是一项操作的重放身份。不同身份可以相同,但不能未经推导就混为一个字段。
唯一约束、外键、条件更新和事务提供的数据不变量范围不同。约束通常在权威数据库中最终裁决,缓存或搜索投影不能代替唯一约束。
订单、支付与搜索:谁有权说"是真的"
订单服务拥有订单状态,支付系统拥有支付受理/清算事实,搜索拥有派生检索视图。身份至少区分业务订单ID、支付尝试ID、事件ID与投影版本;把每次HTTP重试都变成新订单会放大副作用,把所有不同尝试共用一个不可区分状态又会妨碍恢复。
以余额为例,权威写域应同时维护账户、唯一业务单与流水;查询缓存只报告一个带版本的视图。跨服务引用使用稳定业务身份而非跨库直接更新对方内部表。技术上能访问不表示拥有修改权,否则多个服务升级时约束被绕过。
拆分的真实代价与候选方案
方案A保留模块化单库事务,用明确表所有权、API和权限限制外部写;方案B拆成独立库,通过事件和状态机协作。A降低跨资源恢复成本,却需防止共享表无边界蔓延;B增加自治,但要付出投影延迟、模式演进、对账、重放和数据授权成本。不能以"微服务必须独库"直接改变原子约束。
假设原流程单事务10ms,拆分后两次跨区RPC各80ms,即使数据库工作不变,也已增加网络预算并产生未知结果窗口。是否值得要与部署频率、团队依赖和故障域一起测量,而不是只比较SQL耗时。
具体产物与验证
为每个字段列创建者、写入者、读取者、约束、保留与删除责任。模拟订单取消与支付成功晚到,写出允许终态和恢复者;如果两个团队都说由对方修复,数据所有权尚未定义。教学验证使用版本化事件构建搜索投影,重放旧事件与删除事件,断言事实源不被投影反向覆盖;没有唯一身份和版本时,单纯"每天对账"无法证明重复意图与合法新动作的区别。
子域、限界上下文与聚合不变量:把"按业务拆分"落到可执行规则
本页前面讲了"谁有权说什么是真的",但权威边界怎么找出来?DDD 给的答案是先划子域、再定限界上下文、最后用聚合固定不变量。这三步不是命名练习:子域决定投入,上下文决定模型边界,聚合决定一个事务能改什么。
三类子域按对业务的价值划分。核心域(订单计价与库存裁决)值得自建模型、写不变量、配最强的人;支撑子域(通知模板渲染)可以买或外包;通用子域(身份目录、文件存储)优先用成熟产品。把支撑子域当核心域自研,是把成本花在别人的问题上。
限界上下文是模型生效的边界,不是组织架构图。同一个"订单"在结算上下文里是金额与优惠的聚合,在物流上下文里是收件地址与时效的聚合——同名不同义。跨上下文唯一允许共享的是稳定身份(订单号)与集成事件,不是数据库表,也不是内部字段。判定一个边界是否成立,看三件事:上下文内是否有唯一语言(同名概念只表示一件事)、是否有单一写入者、跨边界是否只通过 ID 与事件交互。
聚合是用来保护不变量的最小一致性边界。订单聚合的规则可以写成守卫:行项不超过 5 个、总额不超过 10,000、只有 DRAFT 能提交、已支付订单不能取消(只能退款)、同一提交命令重复到达不产生第二个事件。一个事务只改一个聚合;跨聚合的一致性交给领域事件与最终一致,而不是分布式事务。
执行 python3 labs/context_model.py aggregate_invariants:模型按命令序列驱动聚合,预期输出 total=8000、over_cap=REJECTED_CAP、pay_before_submit=REJECTED_STATE、duplicate_submit=IDEMPOTENT、cancel_after_pay=REJECTED_STATE,事件只有 OrderSubmitted 与 OrderPaid 两条。脚本是确定性状态机,不连数据库,不证明并发下的隔离行为。
两个合理选择:A 贫血模型,把规则放在服务层,聚合只是数据袋,简单但规则会散落且容易被旁路写绕过;B 聚合根持有守卫,所有命令经它裁决,规则集中、可测试,代价是加载边界更大、跨聚合操作需要显式设计。本例选 B,并约定:绕过聚合根的批量脚本与运维入口必须复用同一守卫或记为受控例外。
架构师变化条件
把支付从"订单服务裁决"改为"支付系统裁决、订单订阅":终态机与幂等键的所有权随之迁移,旧消费者在过渡期可能读到双方都不承认的中间态,需要版本化状态契约与兼容窗口。若引入第二个支付通道,同一业务单出现两条受理记录,需按通道区分尝试身份并定义裁决顺序——每次所有权变化重写字段责任表与终态机。
闭卷验收
本页 8 道闭卷题收进了下方自测面板:先回忆,再展开参考答案与评分点,最后给三档自评。