边界、DDD(领域驱动设计)、康威定律、数据所有权与集成
本册回答的不是“微服务怎样实现”,而是“业务、数据、团队和契约为什么应在这里分界,以及边界失效后谁负责恢复”。分布式事务、消息可靠性、服务治理和发布机制不在此重复,分别链接到 DDD(领域驱动设计)与数据所有权、分布式事务与补偿 和 契约兼容与灰度。
1. 定位、事实等级与决策主线
本册把事实分为四级:E1(源码与可复现证据)只用于本地文件、已渲染图和可重复审计;E2(已有材料映射)表示简历项目及已完成知识分册支持该候选叙述,但不证明真实生产采用;E3(演练证据)必须公开输入、公式和结论;E0(待核对)明确缺少源码、配置、运行指标、事故记录或组织访谈。任何“提升百分比”“线上已采用”都不能由 E2(已有材料映射)或 E3(演练证据)推出。
| 决策层 | 核心问题 | 必要证据 | 失败信号 |
|---|---|---|---|
| 业务边界 | 哪组能力使用同一套语言和不变量 | 业务事件、规则冲突、变化原因 | 同词异义、跨域改字段 |
| 数据边界 | 谁能写、谁能解释、谁负责修复 | 写入口、约束、审计、对账 | 多写者、旁路脚本 |
| 集成边界 | 跨域传命令、查询还是事实 | 时效、失败语义、契约版本 | 超时猜结果、事件当命令 |
| 组织边界 | 哪个团队能端到端拥有变化 | 沟通路径、值班、认知负荷 | 每次发布跨多团队排队 |
| 部署边界 | 哪些运行单元需要独立伸缩或隔离 | 流量、故障域、发布频率 | 为拆而拆、分布式单体 |
完整机制复习顺序是:先看 单体到微服务的成本模型,再看本册的决策框架,最后按问题进入 跨域事务与补偿 或 项目事故排障。
2. 从业务语义到边界
2.1 业务能力、子域、Bounded Context(限界上下文)与 Ubiquitous Language(通用语言)
业务能力描述组织长期“能做什么”,例如计价、库存承诺、收款、履约编排;Subdomain(子域)是问题空间中的业务板块,可分核心、支撑和通用;Bounded Context(限界上下文)是某套模型和 Ubiquitous Language(通用语言)保持一致含义的解空间边界。三者不能按组织图、菜单或数据库表机械一一对应。一个 Subdomain(子域)可能因规则、地区或生命周期不同形成多个 Bounded Context(限界上下文),多个低差异通用能力也可能暂时留在同一部署单元。
边界决策从“同一个词是否表达同一个事实”开始。交易域的“完成”可能表示商业承诺成立,支付域的“成功”表示渠道事实已确认,库存域的“可用”表示可以继续承诺的数量,履约域的“完成”表示包裹交付。若把这些状态压成公共枚举,表面统一会隐藏不同状态机、责任人和恢复动作。Ubiquitous Language(通用语言)不是术语表,而是代码、契约、评审、告警和业务沟通共同遵守的局部语义。
| 概念 | 回答的问题 | 边界证据 | 常见误判 |
|---|---|---|---|
| 业务能力 | 组织长期需要完成什么结果 | 价值流、变化原因、责任 | 按现有系统菜单定义 |
| Subdomain(子域) | 问题空间有哪些业务板块 | 专业规则、差异化程度 | 每个名词都建一个域 |
| Bounded Context(限界上下文) | 哪套模型在何处有效 | 语言、模型、不变量、契约 | 等同于服务或代码包 |
| Ubiquitous Language(通用语言) | 团队如何无歧义描述事实 | 事件、状态、代码和告警 | 只维护一份词汇表 |
flowchart LR
A["业务结果与价值流"] --> B["识别业务能力"]
B --> C["划分核心、支撑与通用子域"]
C --> D["检查同词异义与规则冲突"]
D --> E["形成局部模型与通用语言"]
E --> F["决定限界上下文"]
F --> G["再评估服务与部署边界"]图解读:节点从业务结果走向局部模型,箭头刻意把服务和部署放在最后;前提是能访问业务事件、规则和责任人;失败路径是先按技术组件命名服务,再反向拼业务;结论是语义边界先于物理边界。
数据演绎 1:用语义冲突而不是名词数量划界
E3(演练证据):访谈得到 24 个高频词,其中 9 个在交易、支付、库存、履约四组人员中含义不同;再检查 18 条关键规则,11 条只在单域内变化,4 条跨域但可通过事实契约表达,3 条必须同步裁决。语义冲突率为 9÷24=37.5%,局部规则率为 11÷18≈61.1%。这支持先形成四个候选 Bounded Context(限界上下文),但不能直接推出四个服务;还需团队、容量和故障证据。
热门面试题
- 问题:业务能力、Subdomain(子域)与 Bounded Context(限界上下文)有什么区别?
- 考点:问题空间与解空间。
- 回答思路:按长期能力、问题分类和模型适用范围回答。
- 详细答案:业务能力说明组织要持续完成的结果;Subdomain(子域)把问题空间按专业规则和差异化价值分类;Bounded Context(限界上下文)规定某套模型、语言和不变量在哪里有效。它们有关联,但不要求一一对应,更不天然等同于服务。
- 进阶追问:核心子域一定要拆成微服务吗?
- 进阶回答:不一定;核心子域值得保护模型和团队注意力,但部署是否独立还取决于发布、容量、故障隔离和运行成本。
- 问题:怎样判断两个团队说的是不是同一个“订单完成”?
- 考点:Ubiquitous Language(通用语言)。
- 回答思路:用触发事实、允许动作、撤销方式和责任人校验。
- 详细答案:分别追问完成由什么事实触发、完成后允许扣款还是发货、是否可撤销、失败由谁修复。若答案不同,就应使用不同名称或置于不同 Bounded Context(限界上下文),不能只共享一个状态码。
- 进阶追问:统一数据字典能解决吗?
- 进阶回答:只能发现冲突;真正解决要把局部语义落实到模型、契约、权限、告警和责任边界。
- 问题:项目中怎样落地业务能力分析?
- 考点:从价值流到候选边界。
- 回答思路:以交易、支付、库存、履约的变化原因举例。
- 详细答案:先串业务事件和决策点,再记录每条规则由谁提出、为何变化、影响什么不变量。规则共同变化且使用同一语言的能力先归组,跨组只传必要命令、查询或事实,最后再用团队和运行证据决定是否物理拆分。
- 进阶追问:页面流程跨四域是否说明边界错了?
- 进阶回答:不是;用户旅程天然跨域,关键是每一步事实和写责任清楚,而不是让一个模型包办全流程。
2.2 Entity(实体)、Value Object(值对象)、Aggregate(聚合)、Aggregate Root(聚合根)与不变量
Entity(实体)以持续身份区分,Value Object(值对象)以属性整体表达语义,Aggregate(聚合)是一致性边界,Aggregate Root(聚合根)是外部修改该边界的唯一入口。不应把它们当作模式清单逐表映射。架构决策真正关心的是:哪条业务不变量必须在一次本地提交内成立,哪些引用只需保存标识,哪些跨域规则必须改为流程状态、补偿或对账。
聚合过大会放大锁竞争、加载成本和团队冲突;聚合过小会把本应原子成立的不变量推成跨域协调。库存可把“某库存单元的可用、预占、占用守恒”放在一个聚合边界,而不把商品详情、订单、支付和履约都装进来。交易只保存库存预占标识和支付单标识,不直接持有对方对象图。更完整的战术机制复用 聚合与不变量权威章节。
| 对象 | 身份或相等性 | 主要责任 | 边界风险 |
|---|---|---|---|
| Entity(实体) | 稳定身份 | 承载生命周期 | 把所有字段变化都塞入单体 |
Value Object(值对象) | 属性整体相等 | 表达金额、地址快照、数量 | 可变共享导致语义漂移 |
| Aggregate(聚合) | 一致性边界 | 同次提交守不变量 | 过大锁竞争、过小跨域事务 |
| Aggregate Root(聚合根) | 聚合唯一入口 | 校验命令和状态转换 | 外部绕过入口改内部对象 |
flowchart TD
A["列出业务不变量"] --> B{"必须同次提交成立吗"}
B -->|"是"| C["放入一个聚合边界"]
B -->|"否"| D["用流程状态或领域事实协调"]
C --> E["仅由聚合根接收修改命令"]
D --> F["定义补偿、对账与责任时限"]
E --> G["检查竞争与加载成本"]
F --> G图解读:节点从不变量而非数据表出发;箭头区分本地原子规则与跨域流程规则;前提是能说清失败后业务损失;失败路径是把跨域对象引用伪装成一个巨型 Aggregate(聚合);结论是聚合帮助决定本地一致性边界,不直接决定服务数量。
数据演绎 2:聚合大小的权衡
E3(演练证据):某库存聚合原含商品、仓、库存、订单行和履约任务,单次命令平均加载 120 行、热点锁等待 180 毫秒;按“库存守恒必须原子,订单与履约只引用标识”收缩后平均加载 18 行、锁等待演练为 35 毫秒,但新增跨域待确认状态和对账责任。加载下降 (120-18)÷120=85% 只是候选收益,不能抵消新增恢复复杂度。
热门面试题
- 问题:Aggregate(聚合)最重要的判断依据是什么?
- 考点:不变量与事务边界。
- 回答思路:先问必须同次提交成立的规则。
- 详细答案:先列出即使进程崩溃也不能短暂破坏的不变量,把能由同一权威存储原子维护的对象纳入边界;仅需最终收敛的规则通过状态机、事实、补偿和对账处理。表关联和对象导航不是首要依据。
- 进阶追问:聚合能跨数据库吗?
- 进阶回答:概念上不应依赖跨资源原子性;一旦跨资源,通常说明边界过大或规则应改成流程不变量。
- 问题:为什么 Aggregate Root(聚合根)要作为唯一入口?
- 考点:不变量保护。
- 回答思路:说明绕过入口的后果。
- 详细答案:唯一入口让命令在状态转换前统一校验版本、权限和不变量,并在一次提交内写状态与审计。外部直接修改内部 Entity(实体)会让局部字段合法但整体规则失效,也无法定位责任。
- 进阶追问:只靠代码封装够吗?
- 进阶回答:不够;还要用数据库约束、账号权限、审计和测试阻止批处理或其他服务旁路写。
- 问题:交易、库存、支付是否应放在一个 Aggregate(聚合)?
- 考点:跨域不变量。
- 回答思路:区分局部原子规则与端到端流程。
- 详细答案:不应为追求一次提交而形成巨型 Aggregate(聚合)。库存守数量,支付守金额与渠道事实,交易守商业状态;端到端通过意图、稳定标识、状态机、补偿和对账收敛。
- 进阶追问:这样是否降低一致性?
- 进阶回答:是把不同不变量放回各自权威边界,并显式管理跨域窗口,不是放弃正确性。
3. 数据所有权与四域拆分
3.1 数据所有权、单写者原则与写责任
数据所有权包含四项责任:定义语义、接受写命令、维护不变量、承担修复与审计。Single Writer Principle(单写者原则)要求某一业务事实在任一迁移阶段只有一个权威写入方;其他上下文可以缓存、复制、投影和提出命令,但不能绕过所有者直接改表。单写者不等于单实例,也不等于只有一个物理进程,而是写入裁决归同一业务边界。
“谁建了表”“谁读得最多”“谁离数据库最近”都不能决定所有权。订单确认地址是交易或履约承诺时的 Value Object(值对象)快照,客户联系地址则可随客户资料变化;字段相似不代表同一事实。迁移所有权必须同时迁移写接口、数据库权限、契约、修复任务、告警和人员责任,否则只是代码移动。
| 所有权维度 | 所有者必须承担 | 非所有者允许做 | 失败表现 |
|---|---|---|---|
| 语义 | 定义字段、状态和生命周期 | 保存必要快照或投影 | 同字段不同解释 |
| 写入 | 校验命令和版本 | 调用命令接口 | 旁路更新共享表 |
| 正确性 | 维护不变量与审计 | 校验本地消费结果 | 多方互相甩锅 |
| 恢复 | 补偿、对账、人工升级 | 重建自身投影 | 事故无人收口 |
sequenceDiagram
participant 调 as 调用上下文
participant 所 as 数据所有者
participant 库 as 权威存储
participant 订 as 订阅上下文
调->>所: 提交带业务键和期望版本的命令
所->>库: 校验不变量并原子写状态与待发布事实
库-->>所: 返回权威版本
所-->>调: 返回确定结果或可查询处理中
所-->>订: 发布带版本的领域事实
订->>订: 幂等更新本地投影
alt 发现差异
订->>所: 以业务键查询或提交纠正请求
所->>库: 按权威事实裁决
end图解读:节点明确调用方、所有者、权威存储和订阅方;箭头把所有修改收敛到所有者命令;前提是业务键、版本和权限可验证;失败路径由非所有者查证而非直接修表;结论是数据所有权同时包含写权和修复责任。
数据演绎 3:多写者的分叉窗口
E3(演练证据):交易和履约两个服务各以每秒 20 次更新同一地址表,任一写失败概率演练为 0.5%。一分钟有 20×2×60=2400 次写,至少一次失败的期望数约为 2400×0.005=12。即使重试把技术失败降到 0.05%,仍约有 1.2 次分叉,而且乱序覆盖无法由重试解决。改单写者后跨域更新变成命令与带版本事实,失败可停留在可查状态。
热门面试题
- 问题:数据所有权怎样判断?
- 考点:语义、写入、不变量和恢复。
- 回答思路:避免用建表者或读者数量判断。
- 详细答案:看谁定义该事实的业务含义和生命周期,谁有权接受修改命令,谁维护约束与审计,事故后谁负责修复和对账。四项不一致时说明所有权未真正建立。
- 进阶追问:报表团队读得最多,能否成为所有者?
- 进阶回答:读取量不产生写权;报表拥有自己的投影和指标语义,但源业务事实仍由产生它的领域拥有。
- 问题:Single Writer Principle(单写者原则)是否限制吞吐?
- 考点:逻辑写权与物理扩展。
- 回答思路:区分业务裁决和实例数量。
- 详细答案:单写者是逻辑权威边界,可有多实例、分片和并行处理;只要同一业务键由一致规则裁决即可。真正受限时应分区业务键或重构不变量,而不是开放旁路写。
- 进阶追问:数据库管理员能直接修数据吗?
- 进阶回答:紧急操作也应由所有者提供受审计修复命令或脚本,生成修正流水并复核,不能无痕覆盖。
- 问题:如何迁移共享表的所有权?
- 考点:写权迁移。
- 回答思路:盘点写者、冻结语义、单写切换、校验和收权。
- 详细答案:先审计全部应用、批处理和人工账号,确定目标所有者和新契约;新增命令入口和版本字段,旁路写逐个切换,迁移期用日志或投影比对,最后撤销旧账号写权限并演练恢复。
- 进阶追问:什么时候迁移才算完成?
- 进阶回答:写流量、权限、契约、告警、对账和责任人全部收口,旧路径无法再产生新事实时才完成。
3.2 交易、支付、库存、履约四域完整拆分
四域拆分的目的不是四个服务,而是四类不变量和失败责任不混淆。交易域拥有购买意图、订单行、商业状态和取消资格;库存域拥有库存单元、预占流水、可用数量和释放裁决;支付域拥有支付单、商户请求号、渠道事实、金额币种与未知状态;履约域拥有履约单、仓配任务、面单、轨迹和交付状态。交易可以编排用户旅程,但不能直接改库存余额、伪造支付成功或覆盖承运商轨迹。
端到端状态不应压成一张公共订单大表。交易状态是汇总视图,域内事实仍由各域保留;支付成功而库存已释放时,责任不是“回滚四张表”,而是由流程所有者发起重新预占或退款,支付域负责退款事实,库存域负责数量,履约域负责是否已产生不可逆作业。具体补偿机制链接 事务、补偿与消息一致性。
| 领域 | 权威事实 | 核心不变量 | 不能拥有的事实 |
|---|---|---|---|
| 交易 | 订单意图、订单行、商业状态 | 同一意图不重复成单,状态合法推进 | 渠道扣款、库存余额 |
| 库存 | 库存单元、预占与释放流水 | 可用、预占、占用和修正守恒 | 支付成功、承运商签收 |
| 支付 | 支付单、渠道交易、金额币种 | 不重复扣款,金额币种一致 | 订单可发货、库存可用 |
| 履约 | 履约单、仓配任务、面单轨迹 | 作业不重复,状态不倒退 | 资金入账、交易定价 |
sequenceDiagram
participant 用 as 用户
participant 交 as 交易域
participant 库 as 库存域
participant 支 as 支付域
participant 履 as 履约域
用->>交: 创建购买意图
交->>库: 预占库存命令
库-->>交: 预占标识或拒绝
交->>支: 创建支付命令
支-->>交: 已确认或未知可查询
alt 支付已确认且预占有效
交->>履: 创建履约命令
履-->>交: 返回履约标识
else 支付确认时预占已失效
交->>库: 尝试重新预占
alt 仍不可预占
交->>支: 创建退款命令
end
end图解读:节点分别承担商业、数量、资金和作业事实;箭头传命令与标识,不共享内部对象;前提是每域有幂等键和可查询状态;失败路径显式处理支付确认与库存失效竞态;结论是流程可以跨域,但每个事实只有一个解释者。
数据演绎 4:四域状态组合为何不能压成一个枚举
E3(演练证据):若交易有 5 个状态、库存预占有 4 个、支付有 6 个、履约有 7 个,理论组合为 5×4×6×7=840。即使只允许 15% 的合法组合,也有 126 种。把它们压成 12 个公共状态会丢失“支付未知但库存有效”“已退款但履约拦截中”等恢复信息;保留域内状态并定义少量流程里程碑更可审计。
热门面试题
- 问题:交易、支付、库存、履约为什么要分开建模?
- 考点:不同不变量和事实来源。
- 回答思路:从商业、资金、数量和作业四类事实回答。
- 详细答案:四类事实由不同规则、外部证据和责任人维护,失败后的恢复动作也不同。分开建模能让每域用本地事务守住不变量,流程层只协调状态,不以公共大表伪造原子性。
- 进阶追问:是否必须四个数据库?
- 进阶回答:不必须;先建立逻辑所有权和模块权限,再依据隔离、发布和容量决定物理数据库。
- 问题:交易域能否直接把支付改成成功?
- 考点:权威事实。
- 回答思路:区分请求意图和渠道确认。
- 详细答案:不能。交易只能请求支付并保存支付标识或汇总状态,支付域根据渠道回调、主动查单和金额校验确认事实;超时必须保持未知,不能由交易猜测。
- 进阶追问:用户页面需要一个状态怎么办?
- 进阶回答:建立面向旅程的读模型,展示里程碑和处理中原因,但关键动作仍回各权威域校验。
- 问题:支付成功但库存失效由谁负责?
- 考点:流程责任与域责任。
- 回答思路:分开讲流程决策和局部执行。
- 详细答案:交易或专门流程所有者决定重新预占还是退款;库存域只裁决数量,支付域只执行并记录退款,履约域判断是否已有作业。每一步有期限、状态和人工升级。
- 进阶追问:能否直接数据库回滚?
- 进阶回答:不能回滚已发生的外部资金或仓内事实,只能用新的补偿业务动作并保留审计。
4. 跨域协作方式
4.1 跨域查询、命令与事件的语义选择
Query(查询)请求信息,不应改变业务状态;Command(命令)表达希望某个所有者执行动作,可以被接受、拒绝或保持处理中;Event(事件)陈述已经发生的事实,生产者拥有事实语义,消费者决定自己的反应。三者不能只按传输协议区分:通过消息发送“立即扣库存”仍是 Command(命令),通过同步接口返回“支付已确认”也可能是查询到的事实。
跨域查询优先回答“是否需要最新权威值”。关键写前校验应回所有者,列表和报表可使用带新鲜度的 Read Model(读模型)。命令必须有稳定业务键、期望版本、授权和可查询结果;事件必须有事件标识、业务标识、发生时间、业务版本和最小必要语义。事件不是数据库行镜像,也不能要求所有消费者采用同一内部模型。
| 交互 | 发起者意图 | 接收者责任 | 典型失败 |
|---|---|---|---|
| Query(查询) | 获取信息 | 返回权威值或标明新鲜度 | 陈旧读驱动关键写 |
| Command(命令) | 请求改变状态 | 裁决、幂等、返回可查状态 | 超时后换键重试 |
| Event(事件) | 通知已发生事实 | 按自身规则消费 | 把事件当远程命令 |
| Read Model(读模型) | 面向场景组合信息 | 标明来源、版本和更新时间 | 被误当写权威 |
sequenceDiagram
participant 前 as 交易域
participant 所 as 库存所有者
participant 读 as 订单读模型
前->>读: Query(查询)列表可售提示
读-->>前: 返回数量摘要与更新时间
前->>所: Command(命令)确认预占
alt 不变量满足
所-->>前: 返回预占标识与版本
所-->>读: Event(事件)库存已预占
读->>读: 更新旅程投影
else 不变量不满足
所-->>前: 拒绝并返回当前裁决
end图解读:节点把提示性读、权威命令和事实投影分开;箭头表明 Read Model(读模型)不能确认库存;前提是命令可幂等且读模型暴露新鲜度;失败路径由所有者拒绝;结论是交互语义比同步或异步传输更先决定责任。
数据演绎 5:陈旧查询与关键确认
E3(演练证据):库存读模型每 2 秒刷新,热点商品每秒可能被确认 30 件,则最坏陈旧窗口内可变化 2×30=60 件。若列表展示 80 件,用户看到可售并不等于确认必成;最终命令必须在权威库存做条件裁决。把读模型直接作为扣减源会把 60 件窗口风险变成超卖可能。
热门面试题
- 问题:怎样区分 Command(命令)和 Event(事件)?
- 考点:意图与事实。
- 回答思路:看是否要求特定接收者执行动作。
- 详细答案:Command(命令)表达请求,接收者有裁决责任;Event(事件)表达已经发生的事实,不指定消费者必须怎样处理。名称带“事件”但语义是“请扣库存”,仍然是命令。
- 进阶追问:事件能失败吗?
- 进阶回答:事实本身已发生,发布和消费可能失败;应补发布、重试消费或人工恢复,不能撤销历史事实名称。
- 问题:跨域查询什么时候用 Read Model(读模型)?
- 考点:权威读与场景读。
- 回答思路:按新鲜度、组合复杂度和写前校验区分。
- 详细答案:多域列表、搜索和报表适合本地 Read Model(读模型),并标明更新时间;会形成资金、库存或履约承诺的动作,必须回权威域重新校验。
- 进阶追问:投影延迟如何展示?
- 进阶回答:返回业务版本或更新时间,对超窗结果降级、回源或禁止关键操作。
- 问题:事件契约最少应包含什么?
- 考点:可去重、可排序、可解释。
- 回答思路:列标识、版本、时间和最小语义。
- 详细答案:至少包含事件标识、业务对象标识、事件类型、业务版本、发生时间、契约版本和消费者完成决策所需的最小字段;敏感信息与内部表结构不应无界暴露。
- 进阶追问:消费者缺字段怎么办?
- 进阶回答:先判断是否属于稳定事实语义;可兼容扩展契约,否则由消费者回权威接口查询或定义新事件。
4.2 同步与异步集成的选择边界
同步集成适合调用方必须在当前时间预算内得到权威裁决,且依赖可用性、延迟和并发能够承受;异步集成适合允许“已受理”、需要削峰隔离、存在长时外部动作或一个事实被多个上下文消费。两者不是快慢之争,而是用户承诺、失败语义和恢复责任的选择。同步超时会产生未知结果,异步受理会产生积压、重复、乱序和长期处理中。
常见组合是同步接受意图并持久化,异步完成后续动作;或先同步权威校验,再异步构建投影。支付、承运商下单等外部动作即使使用同步协议,也必须把超时当未知并主动查证。消息投递和补偿机制复用 分布式事务与消息一致性,本节只决定承诺形式和责任。
| 维度 | 同步集成 | 异步集成 | 决策问题 |
|---|---|---|---|
| 用户承诺 | 当前返回确定裁决或未知 | 返回已受理并可查询 | 用户能否接受处理中 |
| 耦合 | 运行时同时在线 | 契约和时间解耦 | 下游停机是否阻断入口 |
| 失败 | 超时、级联、未知 | 积压、重复、乱序、死信 | 谁在何时恢复 |
| 观测 | 截止时间、尾延迟 | 最老年龄、净清理能力 | 哪个信号代表业务恢复 |
sequenceDiagram
participant 用 as 用户
participant 入 as 交易入口
participant 本 as 本地权威存储
participant 后 as 履约处理方
用->>入: 提交创建履约意图
入->>本: 原子保存意图与待办
本-->>入: 返回已受理和查询标识
入-->>用: 明确处理中
入-->>后: 异步传递带版本命令
alt 后续完成
后-->>入: 发布完成事实
入-->>用: 查询得到终态
else 超时、重复或失败
后->>后: 复用业务键查证并重试
后-->>入: 保持处理中或升级人工
end图解读:节点把入口承诺、本地事实和后续处理分开;箭头显示“已受理”不是“已完成”;前提是查询标识、幂等和状态年龄可观测;失败路径保持处理中并升级;结论是异步化必须把时间和恢复责任产品化。
数据演绎 6:同步等待还是异步受理
E3(演练证据):承运商响应中位数 300 毫秒、99 分位 8 秒,入口总预算 2 秒;若同步等待,至少尾部 1% 请求超预算且占用线程。峰值每秒 200 请求、平均等待演练为 0.8 秒,需要约 200×0.8=160 个并发槽位。异步受理把入口本地提交控制在 80 毫秒,但要按每秒 200 条到达率设计积压、查证和恢复,不代表总完成更快。
热门面试题
- 问题:同步与异步集成怎样选?
- 考点:承诺、时间预算和恢复。
- 回答思路:先问是否必须当前得到裁决。
- 详细答案:必须在当前交互形成权威承诺且依赖能满足预算时用同步;允许处理中、需要隔离长任务或多消费者时可异步。无论选择哪种,都要定义未知、重试、查证和人工出口。
- 进阶追问:异步一定更可用吗?
- 进阶回答:不一定;它提高入口解耦,却新增积压、重复、乱序、存储和运维责任,恢复能力不足时只是延后失败。
- 问题:同步调用超时是否等于失败?
- 考点:未知结果。
- 回答思路:区分本方观察与对方事实。
- 详细答案:超时只表示本方未在预算内得到响应,对方可能未执行、已执行或正在执行。必须复用原业务键查询,不换键重做,也不直接向用户宣告确定失败。
- 进阶追问:多久后才能重试?
- 进阶回答:由对方幂等能力、查询接口、最坏处理时间和业务损失决定;先查证,再同键有限重试。
- 问题:怎样证明异步链路恢复了?
- 考点:业务恢复信号。
- 回答思路:不只看队列条数。
- 详细答案:同时看最老任务年龄、到达率、有效处理率、净清理时间、失败分类、最终业务状态和差异对账。积压清零但副作用丢失不算恢复。
- 进阶追问:扩消费者就能清积压吗?
- 进阶回答:只有瓶颈不在分区、数据库、外部限额或锁时才有效;扩容前要验证下游余量。
5. 上下文映射与反模式
5.1 ACL(防腐层)、OHS(开放主机服务)、Published Language(发布语言)等上下文映射
Context Map(上下文映射)描述 Bounded Context(限界上下文)之间的权力、依赖和翻译关系,不只是画调用箭头。ACL(防腐层)由下游保护自己的模型,把上游或遗留系统的术语、状态、错误和协议翻译成本域语义;OHS(开放主机服务)由上游提供稳定、面向多消费者的服务能力;Published Language(发布语言)是双方明确发布、版本化并可测试的交换语言。Conformist(遵奉者)表示下游直接接受上游模型,Shared Kernel(共享内核)表示双方共同维护很小一块模型,Customer/Supplier(客户/供应商)则要求上游把下游需求纳入计划。
选择关系要看控制力和变化方向。对不可控承运商或遗留 WMS(仓储管理系统),下游通常需要 ACL(防腐层);对内部库存权威能力,可用 OHS(开放主机服务)加 Published Language(发布语言)提供命令与事件;Shared Kernel(共享内核)只适用于团队关系紧密、变更同步且共享面积极小的部分,例如金额 Value Object(值对象),不能扩成公共领域大模型。翻译层必须保留原始响应摘要、映射版本和无法识别状态,否则错误翻译会悄悄污染本域。
| 映射关系 | 谁承担适配 | 适用条件 | 主要风险 |
|---|---|---|---|
| ACL(防腐层) | 下游 | 上游不可控或语义差异大 | 适配层变成无边界中台 |
| OHS(开放主机服务) | 上游 | 多下游需要稳定能力 | 为单一消费者泄露内部模型 |
| Published Language(发布语言) | 双方按发布契约 | 交换语义需长期演进 | 只发文档、不做兼容测试 |
| Shared Kernel(共享内核) | 双方共同维护 | 共享面小且协作紧密 | 公共模型持续膨胀 |
| Conformist(遵奉者) | 下游接受 | 无控制力且差异成本低 | 上游变化直接扩散 |
sequenceDiagram
participant 承 as 外部承运商
participant 防 as 履约域 ACL(防腐层)
participant 履 as 履约域
participant 交 as 交易域
承-->>防: 返回外部状态码与原始时间
防->>防: 按映射版本翻译并保留原始摘要
alt 状态可识别
防->>履: 提交本域轨迹事实
履-->>交: 通过发布语言发布里程碑
else 新状态或语义冲突
防->>履: 进入未知状态隔离队列
履-->>交: 保持原状态并暴露待核对
end图解读:节点把外部协议、ACL(防腐层)、履约模型和下游交易分开;箭头显示翻译后才进入本域;前提是映射版本和原始证据可追溯;失败路径隔离未知状态而非猜测;结论是防腐的核心是阻止外部语义无审计扩散。
数据演绎 7:ACL(防腐层)投入是否值得
E3(演练证据):接入 5 家承运商,每家约 18 个外部状态;若 4 个内部消费者各自适配,映射点最多 5×18×4=360 个。集中在履约域 ACL(防腐层)后,外部映射约 5×18=90 个,再发布 8 个稳定里程碑,消费者只理解 8 个内部语义。映射点下降不证明代码一定更少,但能把状态解释和事故责任收敛到一个边界。
热门面试题
- 问题:ACL(防腐层)解决什么问题?
- 考点:模型隔离。
- 回答思路:说明协议转换与语义翻译的区别。
- 详细答案:ACL(防腐层)不只转换字段格式,还把外部状态、错误和生命周期翻译成本域可解释语义,并隔离未知值、记录映射版本和原始证据,防止上游模型直接塑造下游内部结构。
- 进阶追问:ACL(防腐层)应放上游还是下游?
- 进阶回答:保护谁的模型就由谁拥有,通常放在下游边界;上游可另提供 OHS(开放主机服务)。
- 问题:OHS(开放主机服务)和 Published Language(发布语言)有什么关系?
- 考点:能力与交换语言。
- 回答思路:一个是服务面,一个是契约面。
- 详细答案:OHS(开放主机服务)定义上游向多个下游稳定开放什么能力,Published Language(发布语言)定义双方交换的公开语义和版本。服务可以有多种传输,但交换语言必须兼容、可测试和可废弃。
- 进阶追问:内部接口也需要发布语言吗?
- 进阶回答:只要跨团队、跨版本或需独立演进就需要,不能因为在同一公司就默认同步升级。
- 问题:Shared Kernel(共享内核)什么时候危险?
- 考点:共同所有权成本。
- 回答思路:看共享面积和协同频率。
- 详细答案:当共享内容包含状态机、持久化实体或频繁变化规则时,任何一方修改都要求另一方同步,边界会退化为公共大模型。应限制为稳定、很小且共同测试的语义对象。
- 进阶追问:金额对象能共享吗?
- 进阶回答:可共享精度、币种和舍入等稳定语义,但支付状态、账务规则和交易价格策略仍应各域拥有。
5.2 共享数据库、共享表、双写与公共大模型反例
共享数据库本身不是原罪,同库不同 Schema(模式)且权限、迁移和所有权清楚,可以是模块化单体或过渡部署;危险的是多个边界直接写同一张表、共享同一状态机、依赖未发布列和互相修改数据。共享表会把部署、发布、事务、恢复和人员责任重新绑在一起。双写则在两个独立提交之间制造不可消除的失败窗口,重试只能处理部分技术失败,不能解决顺序颠倒和语义冲突。
公共大模型试图让交易、库存、支付、履约都依赖一套 Order(订单)对象,看似减少转换,实际把局部概念压成最低公分母。某域增加字段或状态就触发全链路发布;消费者借对象引用修改不属于自己的事实;事故时没人知道哪个快照权威。正确做法是共享稳定标识和最小 Published Language(发布语言),各域保留自己的 Entity(实体)、Value Object(值对象)、状态机和投影。
| 反例 | 短期便利 | 长期损失 | 收敛方向 |
|---|---|---|---|
| 共享表多写 | 少接口、可联表 | 语义冲突、权限失控 | 单写者与命令入口 |
| 应用层双写 | 快速复制数据 | 部分失败、乱序分叉 | 本地事实加可靠传播 |
| 公共大模型 | 少转换代码 | 发布耦合、模型贫血 | 局部模型加发布语言 |
| 跨域外键对象图 | 导航方便 | 生命周期绑定 | 标识引用与本地快照 |
| 共享数据库账号 | 运维简单 | 无法证明写责任 | 最小权限与审计 |
flowchart TD
A["服务甲写共享表"] --> C["同一业务事实"]
B["服务乙写共享表"] --> C
C --> D{"部分失败或乱序"}
D -->|"是"| E["状态分叉且责任不明"]
D -->|"暂未发生"| F["发布与模式仍被绑定"]
E --> G["确立单写者和修正流水"]
F --> G
G --> H["命令、事件或读模型集成"]图解读:节点显示两个写者争夺同一事实;箭头覆盖立即分叉与暂时正常两条路径;前提是审计全部写源;失败路径不会因增加重试消失;结论是必须先收写权,再谈物理拆库。
数据演绎 8:双写成功率不能证明一致
E3(演练证据):一次业务操作先写交易库再写履约库,两次提交成功率各为 99.9%,若独立,则双写都成功约为 0.999×0.999≈99.8001%,每 10 万次约有 200 次至少一侧失败。即便补偿修复 99%,仍约 2 次残留;若响应丢失导致重复或乱序,独立概率模型还会低估风险。故双写监控必须看业务键差异和年龄,而非两个接口各自成功率。
热门面试题
- 问题:共享数据库一定违反 DDD(领域驱动设计)吗?
- 考点:逻辑与物理边界。
- 回答思路:区分同库和共享写权。
- 详细答案:不一定。同一数据库中按 Schema(模式)、账号、迁移和模块隔离,仍可保持逻辑所有权;真正危险的是跨边界直接写表、依赖内部列和共同发布。物理拆库是强化手段,不是边界成立的唯一证明。
- 进阶追问:何时必须拆库?
- 进阶回答:当权限无法隔离、发布冲突持续、容量或故障域必须独立、合规要求明确时,物理拆分收益才足以覆盖迁移成本。
- 问题:为什么应用双写不可靠?
- 考点:不可原子的失败窗口。
- 回答思路:列部分失败、超时未知和乱序。
- 详细答案:两个提交之间进程可崩溃,任一响应可丢失,重试可能重复,后写可能先到;应用无法用一次本地提交覆盖两个权威源。应先写一个权威事实,再可靠传播并对账。
- 进阶追问:临时双写迁移可以吗?
- 进阶回答:可以,但只允许一个权威,另一侧作影子写或投影;要有差异校验、水位、截止日和退出动作。
- 问题:公共领域模型为什么会形成耦合?
- 考点:局部语义被抹平。
- 回答思路:从状态、发布和写责任说明。
- 详细答案:不同域对同名对象有不同生命周期与不变量,共用类会迫使它们同步升级,并诱导跨域修改内部字段。转换代码减少了,组织协调、发布和事故成本却上升。
- 进阶追问:哪些内容可以公共化?
- 进阶回答:稳定标识、时间和金额等小型
Value Object(值对象)、通用错误外壳可以谨慎共享,业务状态机不能公共化。
6. 组织、服务与部署边界
6.1 Conway’s Law(康威定律)、沟通路径与团队认知负荷
Conway’s Law(康威定律)指出系统结构会映射组织沟通结构;Inverse Conway Maneuver(逆康威操作)则主动设计团队边界以促进期望架构。它不是“一个团队一个服务”的公式,而是提醒:当一次业务变更长期需要跨 5 个团队排队,代码边界再漂亮也会形成协调瓶颈;当一个团队承担过多业务、技术和运行模型,Cognitive Load(认知负荷)又会超过可拥有范围。
团队应能端到端理解业务规则、代码、数据、发布、告警和恢复,但边界大小取决于认知负荷、变化耦合和运行负担。交易与库存若 80% 的变更必须共同发生,强行由两个团队分别排期会制造接口往返;支付涉及资金、渠道和合规,通常值得清晰所有权。Platform Team(平台团队)提供自助能力和护栏,不应成为每次业务发布的工单中介。组织调整本身有成本,应通过变更前置时间、跨团队等待、事故移交和认知调查验证。
| 信号 | 可能问题 | 架构动作 | 组织动作 |
|---|---|---|---|
| 变更长期跨多团队 | 边界与价值流错位 | 重划能力或契约 | 调整端到端所有权 |
| 单团队服务过多 | Cognitive Load(认知负荷)过高 | 合并低价值边界 | 平台化或缩小责任 |
| 平台审批排队 | 平台成为瓶颈 | 标准化自助接口 | 从项目制转产品制 |
| 事故反复移交 | 恢复责任断裂 | 明确权威与告警边界 | 单一事故负责人 |
sequenceDiagram
participant 业 as 业务需求
participant 交 as 交易团队
participant 库 as 库存团队
participant 支 as 支付团队
participant 平 as Platform Team(平台团队)
业->>交: 提交端到端变化
交->>库: 协商库存契约影响
交->>支: 协商支付契约影响
alt 边界与契约稳定
库-->>交: 独立实现并通过契约测试
支-->>交: 独立实现并通过契约测试
交->>平: 自助发布与观测
else 每次都要共同改内部模型
库-->>交: 排队等待联合发布
支-->>交: 反复解释语义
交->>业: 前置时间持续上升
end图解读:节点包括业务、三个域团队和平台;正常箭头依靠稳定契约独立交付,失败箭头暴露共同改内部模型导致的排队;前提是平台可自助、团队能运行自己的边界;结论是组织设计要以价值流和认知负荷验证,而不是照抄组织图。
数据演绎 9:沟通边数与变更等待
E3(演练证据):一次需求涉及 5 个团队,若两两都需同步,潜在沟通边为 5×4÷2=10;每条边平均等待 0.8 天,串行关键路径即使只经过 4 条也约 3.2 天。重划后由一个价值流团队拥有交易与库存局部变化,支付通过稳定契约协作,关键等待边降到 1 条。该算例只说明协调结构,不证明组织调整必然提升效率。
热门面试题
- 问题:Conway’s Law(康威定律)对架构设计有什么启发?
- 考点:系统与沟通结构共演化。
- 回答思路:从变更和事故路径回答。
- 详细答案:系统边界若与实际沟通和责任长期相反,会通过共享库、临时接口和联合发布重新耦合。应观察价值流、跨团队等待和事故移交,再调整团队或契约,使所有权与演进方向一致。
- 进阶追问:能否先画服务再组团队?
- 进阶回答:可以作为假设,但必须验证团队规模、技能、值班和认知负荷;组织承接不了的边界不会自动自治。
- 问题:怎样判断团队 Cognitive Load(认知负荷)过高?
- 考点:可拥有边界。
- 回答思路:看服务数之外的业务、工具和运行负担。
- 详细答案:观察变更前置时间、值班告警、陌生组件比例、恢复依赖、知识集中度和人员反馈。服务数量少但每个模型差异巨大,同样可能超载。
- 进阶追问:增加文档能解决吗?
- 进阶回答:文档降低学习成本,但不能消除过多领域和运行责任;必要时合并边界、平台化通用能力或调整团队责任。
- 问题:平台团队为什么不应成为工单中介?
- 考点:自治与平台产品化。
- 回答思路:区分提供能力和代替执行。
- 详细答案:平台应提供可自助的发布、观测、安全和数据能力及默认护栏,业务团队仍对使用结果负责。每次变更都依赖平台人工操作会集中排队并切断反馈。
- 进阶追问:高风险操作怎么办?
- 进阶回答:用分级权限、审批门禁和自动审计控制,保留少量人工例外,而不是把全部操作改成工单。
6.2 服务边界与部署边界不等价
业务模块、Bounded Context(限界上下文)、服务、进程、容器和数据库是不同维度。一个 Bounded Context(限界上下文)可以先以模块化单体实现,也可因读写热点拆成多个部署单元;多个低变化上下文可以共用一个进程,但保持代码、数据权限和契约边界。服务边界强调业务能力和所有权,部署边界强调独立发布、伸缩和故障隔离。
把每个 Aggregate(聚合)变成服务会产生大量网络跳数和跨域事务;把所有模块打进一个部署也不等于边界不存在。决策时分别问:是否需要独立发布、是否有不同扩缩容曲线、是否要隔离故障、是否有安全或合规要求、团队是否能独立运行。只有收益覆盖接口、观测、兼容、值班和恢复成本时才物理拆分。
| 层次 | 核心目的 | 可否一对多 | 验证信号 |
|---|---|---|---|
| Bounded Context(限界上下文) | 保护模型语义 | 可对应多个部署 | 语言与规则独立 |
| 业务服务 | 暴露能力与所有权 | 可有多实例 | 契约和责任明确 |
| 部署单元 | 独立发布与隔离 | 可承载多个模块 | 发布、容量、故障收益 |
| 数据存储 | 持久化与约束 | 可同库隔离或独库 | 权限、恢复、容量 |
flowchart LR
A["交易限界上下文"] --> B["交易写模块"]
A --> C["交易查询模块"]
B --> D["部署单元一"]
C --> E["部署单元二"]
F["低变化支撑上下文"] --> D
D --> G["同库分模式与账号"]
E --> H["独立读模型存储"]图解读:节点故意展示一个 Bounded Context(限界上下文)可有两个部署单元,一个部署单元也可承载隔离模块;箭头不表示共享写权;前提是模块依赖和账号权限可验证;失败路径是把图中每个框都机械建成服务;结论是逻辑边界与运行拓扑可独立演进。
数据演绎 10:独立部署收益是否覆盖成本
E3(演练证据):交易查询峰值是写入的 12 倍,若同部署需整体从 6 个实例扩到 18 个;拆查询部署后,写模块保留 6 个、查询模块 10 个,共 16 个,少 2 个实例。但新增契约、监控、发布和值班每月演练成本折算 5 人日。若 2 个实例月成本仅 2000 元且故障隔离收益没有证据,拆分不一定值得;若查询故障频繁拖垮写入,则隔离收益可能成为硬理由。
热门面试题
- 问题:Bounded Context(限界上下文)是否等于微服务?
- 考点:逻辑与部署维度。
- 回答思路:明确两者目的不同。
- 详细答案:不等于。Bounded Context(限界上下文)保护模型和语言,微服务是独立部署和运行单元;前者可在模块化单体中成立,也可因容量把同一上下文拆成多个部署。
- 进阶追问:什么时候从模块拆成服务?
- 进阶回答:当独立发布、伸缩、故障或安全收益有证据,且团队能承担契约、观测、恢复和值班成本时。
- 问题:一个部署单元能承载多个上下文吗?
- 考点:物理合并与逻辑隔离。
- 回答思路:说明模块和权限条件。
- 详细答案:可以,尤其在团队小、变化低和运行成本敏感时;但代码依赖、数据写权、测试和契约要保持清楚,不能因同进程就任意调用内部对象。
- 进阶追问:这还是单体吗?
- 进阶回答:可能是模块化单体;名称不重要,关键是边界能否独立理解、测试、演进并在需要时拆出。
- 问题:为什么每个 Aggregate(聚合)一个服务通常不合理?
- 考点:粒度与分布式成本。
- 回答思路:从事务、调用和团队所有权回答。
- 详细答案:Aggregate(聚合)是局部一致性边界,粒度常比可运营业务能力小;逐个服务化会放大调用跳数、兼容、观测和事务协调,团队也难为每个小服务建立独立责任。
- 进阶追问:聚合之间怎样调用?
- 进阶回答:同上下文内可由应用服务协调,跨上下文则通过明确命令、查询或事件,不由对象直接跨边界导航。
7. 契约与跨域一致性责任
7.1 版本契约、Schema Evolution(模式演进)、兼容与废弃
跨域契约是团队之间的可执行承诺,包括语义、字段、错误、幂等、顺序、时效、安全和废弃规则。Compatibility(兼容性)至少分向后兼容、向前兼容和完全兼容:新消费者能否读旧数据、旧消费者能否读新数据、双方能否独立滚动。Schema Evolution(模式演进)不只是加字段;字段含义、默认值、单位、枚举、可空性、标识和事件时序变化都可能破坏行为。
推荐 Expand/Migrate/Contract(扩展/迁移/收缩):先添加可选字段或新端点,新旧双方都能工作;再双读或双发并按业务键校验迁移水位;确认旧消费者、历史重放、回滚窗口和数据修复完成后,才停止旧写、撤销旧读并废弃。破坏性语义应发布新事件类型或主版本,不能把旧字段悄悄改义。Deprecated(已废弃)必须有所有者、调用清单、截止日、通知、阻断门禁和例外审批,文档标记不等于完成退役。
| 变更 | 常见兼容判断 | 安全动作 | 禁止动作 |
|---|---|---|---|
| 新增可选字段 | 旧消费者可忽略 | 先扩展后使用 | 立即设为必填 |
| 枚举新增值 | 旧代码可能不识别 | 未知值隔离或兜底 | 默认映射成功 |
| 字段改名 | 历史数据仍用旧名 | 双字段迁移与校验 | 原地覆盖 |
| 语义改变 | 名同义异最危险 | 新事件或新主版本 | 偷改旧定义 |
| 删除字段 | 需证明零读取 | 收缩门禁与观察期 | 仅凭代码搜索删除 |
sequenceDiagram
participant 生 as 契约生产者
participant 旧 as 旧消费者
participant 新 as 新消费者
participant 治 as 契约治理
生->>旧: 扩展阶段发送旧字段和可选新字段
生->>新: 同一业务键发送兼容版本
旧-->>治: 上报旧契约消费水位
新-->>治: 上报新契约结果与差异
alt 差异为零且旧调用归零
治->>生: 批准停止旧写
治->>旧: 到期阻断旧版本
else 仍有旧调用或历史重放失败
治->>生: 保持双版本并修复
治->>新: 回滚到兼容读取
end图解读:节点包含生产者、新旧消费者和治理责任;箭头贯穿扩展、迁移、观测与收缩;前提是业务键和版本水位可见;失败路径保留兼容读写而非强删;结论是废弃是运行迁移,不是文档状态。
数据演绎 11:废弃门禁为什么不能只看百分比
E3(演练证据):旧契约日调用从 100 万降到 0.02%,仍有 1000000×0.0002=200 次;其中若 5% 来自高价值支付补单,就是 10 次关键调用。按百分比直接关闭会遗漏长尾。门禁应同时要求调用方清单归零、连续观察窗无流量、历史重放通过、回滚路径验证和责任人签署。
热门面试题
- 问题:Schema Evolution(模式演进)最危险的变化是什么?
- 考点:语法兼容与语义兼容。
- 回答思路:强调字段含义和枚举变化。
- 详细答案:最危险的是结构看似兼容但语义改变,例如金额单位、时间口径、状态含义或默认值变化。序列化能成功不代表业务能正确解释,因此契约测试要覆盖行为和旧样本。
- 进阶追问:加字段一定兼容吗?
- 进阶回答:不一定;旧消费者若拒绝未知字段,或新字段立即必填、改变计算含义,仍会破坏兼容。
- 问题:怎样执行 Expand/Migrate/Contract(扩展/迁移/收缩)?
- 考点:中间态可运行。
- 回答思路:按扩展、迁移校验、收缩回答。
- 详细答案:先发布兼容结构并保持旧路径;再迁移生产和消费,用同一业务键双读或双发校验;确认调用、水位、重放和回滚后停止旧写,最后撤销旧读与权限。
- 进阶追问:双发是否也是双写风险?
- 进阶回答:是,所以要明确单一权威版本,另一路只作兼容投递或影子校验,并设置截止日和差异处理。
- 问题:契约何时算真正废弃?
- 考点:运行退役证据。
- 回答思路:列零调用、零权限、零回放依赖和回滚证明。
- 详细答案:所有调用方迁移、观察窗内旧流量归零、历史数据和重放策略已处理、旧权限与路由撤销、告警和文档更新后才算完成。只加 Deprecated(已废弃)标记不算。
- 进阶追问:发现未知旧调用怎么办?
- 进阶回答:停止收缩,定位所有者并评估业务影响;不能为赶日期静默丢弃。
7.2 跨域一致性、补偿、对账与责任落点
跨域一致性不是“最终会好”的口号,而是一组有所有者、有期限、有证据的责任。每个域先用本地事务守住自己的不变量;流程所有者维护端到端状态、超时和下一步;产生副作用的域负责幂等、查证和补偿执行;权威数据所有者负责差异裁决;Reconciliation(对账)责任人证明多个事实源是否收敛;无法自动判定的记录进入有时限的人工队列。
补偿是新的业务动作,不是时间倒流。库存释放、支付退款、面单取消都可能再次失败或已不可逆,必须有独立标识、状态机、重试边界和审计。对账不是补偿失败后的最后脚本,而是日常控制:定义左右数据集、业务键、金额或数量守恒、时间水位、差异分类、自动修复白名单和人工升级。具体 Outbox(发件箱)、Saga(长事务模式)等机制复用 分布式事务与补偿。
| 责任 | 所有者 | 必备产物 | 超限动作 |
|---|---|---|---|
| 局部不变量 | 各领域数据所有者 | 约束、状态、审计流水 | 拒绝非法命令 |
| 端到端推进 | 流程所有者 | 流程状态与时限 | 降级或人工升级 |
| 副作用查证 | 发起副作用的领域 | 稳定请求键与查询记录 | 保持未知并查证 |
| 补偿执行 | 拥有被补偿事实的领域 | 独立补偿单 | 重试、替代或人工 |
| Reconciliation(对账) | 明确的数据控制责任人 | 差异、证据和处置结果 | 冻结扩面与事故响应 |
sequenceDiagram
participant 流 as 流程所有者
participant 库 as 库存域
participant 支 as 支付域
participant 账 as Reconciliation(对账)控制
流->>库: 创建库存预占
库-->>流: 返回预占标识
流->>支: 创建支付
alt 支付明确失败
流->>库: 创建释放补偿
else 支付超时未知
流->>支: 复用请求键主动查证
alt 最终成功但库存已失效
流->>支: 创建退款补偿单
else 最终失败
流->>库: 释放预占
end
end
账->>库: 按业务键核对数量流水
账->>支: 按业务键核对金额与渠道事实
账-->>流: 返回差异、年龄与处置责任图解读:节点把流程、局部事实和对账控制分开;箭头表明补偿由事实所有者执行;前提是稳定业务键和水位一致;失败路径覆盖支付未知和库存失效;结论是最终一致性的关键是责任与时限,不是组件名称。
数据演绎 12:差异积压的恢复时限
E3(演练证据):事故 20 分钟内每分钟产生 150 条待核对记录,积压为 20×150=3000。恢复后每分钟新增 50 条,自动核对能力每分钟 250 条,净清理能力为 250-50=200,理论清空需 3000÷200=15 分钟。若人工差异占 4%,仍有 120 条需要按风险分级,不能用自动队列清零宣布业务恢复。
热门面试题
- 问题:跨域最终一致由谁负责?
- 考点:责任分层。
- 回答思路:区分局部事实、流程和对账。
- 详细答案:各域负责自己的不变量和副作用,流程所有者负责端到端推进与超时,对账责任人负责发现并推动差异闭环。不能把责任笼统交给消息平台或定时任务。
- 进阶追问:流程所有者是否可以改各域数据?
- 进阶回答:不能;它只能发命令和记录流程状态,具体事实仍由所属域裁决。
- 问题:补偿为什么不是回滚?
- 考点:现实副作用不可逆。
- 回答思路:用退款和库存释放举例。
- 详细答案:原动作已经成为历史事实,补偿是带新标识和新状态的反向业务动作,可能收费、失败或被拒绝。它必须审计并遵守当前规则,不能删除原流水假装未发生。
- 进阶追问:补偿失败怎么办?
- 进阶回答:保持补偿处理中,有限重试、查证、替代动作或人工升级,并监控最老年龄。
- 问题:对账应该核对什么?
- 考点:业务事实而非行数。
- 回答思路:说明数据集、业务键、守恒和水位。
- 详细答案:明确双方权威数据集和一致水位,按业务键核对状态、金额、数量、版本和外部事实;差异分可自动修复、需业务裁决和证据不足三类,每类有责任人和时限。
- 进阶追问:全量对账太贵怎么办?
- 进阶回答:日常增量加周期全量,按风险分层和分片执行,但资金与库存关键守恒不能只靠抽样。
8. 漂移、排障与演进
8.1 边界漂移、循环依赖与分布式单体排障
Boundary Drift(边界漂移)表现为非所有者开始解释或修改他域事实、契约持续泄露内部列、紧急旁路成为常规入口、读模型反向承担权威写。Circular Dependency(循环依赖)表现为交易同步等库存、库存又同步查询交易,或发布必须按固定顺序联合上线。Distributed Monolith(分布式单体)则兼具网络成本和单体耦合:服务很多,却共享数据库、同步调用链长、无法独立发布、故障和回滚互相绑定。
排障不能只看技术链路。先按用户旅程确定失败时间窗,再画实际运行依赖和写路径,审计数据库账号、内部表访问、契约版本和联合发布记录;用业务键追踪命令、事件、投影和补偿;识别第一个越过边界的事实,而不是最后一个报错服务。止血优先冻结旁路写、阻断循环重试、保留权威流水和未知状态;修复通过所有权收口、异步事实、局部读模型或模块合并减少环;验证要覆盖独立发布、单域故障和数据对账。
| 症状 | 首查证据 | 常见根因 | 架构修复 |
|---|---|---|---|
| 服务独立发布失败 | 发布顺序与契约矩阵 | 循环依赖 | 打断环、兼容契约 |
| 多域数据互相覆盖 | 数据库审计日志 | 多写者 | 收口单写者 |
| 链路超时级联 | 调用图与截止时间 | 同步深链 | 本地决策、异步事实 |
| 事故反复移交 | 告警和责任记录 | 所有权不明 | 端到端责任人 |
| 回滚必须全量回滚 | 版本和共享模式 | Distributed Monolith(分布式单体) | 合并或真正解耦 |
flowchart TD
A["用户旅程失败"] --> B["冻结时间窗与业务键"]
B --> C["还原实际调用、写入与发布图"]
C --> D{"是否存在旁路写或依赖环"}
D -->|"有"| E["冻结写、停止重试、保护权威流水"]
D -->|"无"| F["检查契约版本、投影水位与责任时限"]
E --> G["收口所有权或合并伪边界"]
F --> H["修复兼容、重建投影或补偿"]
G --> I["独立发布与故障注入验证"]
H --> I图解读:节点从业务失败还原实际依赖,不相信静态架构图;箭头优先处理旁路写和循环放大;前提是有业务键、审计和版本证据;失败路径保护权威事实;结论是有些分布式单体应合并,有些必须打断依赖,不能一律继续拆。
数据演绎 13:同步调用链的联合可用性
E3(演练证据):交易请求同步经过 6 个服务,每个在目标窗口内可用率演练为 99.9%,若近似独立,整链可用率为 0.999^6≈99.4015%,约 0.5985% 请求至少遇到一处失败;若每层再重试两次,下游流量最坏可放大。该计算不证明真实独立性,只说明长同步链会累计失败面,应减少关键路径依赖并传播截止时间。
热门面试题
- 问题:怎样识别 Distributed Monolith(分布式单体)?
- 考点:分布式部署与强耦合并存。
- 回答思路:看数据、调用、发布、故障和团队。
- 详细答案:典型信号是共享表多写、长同步链、固定联合发布、单服务故障拖垮全链、无法独立回滚,以及团队仍按技术层协作。服务数量不是判断依据。
- 进阶追问:应该继续拆还是合并?
- 进阶回答:先看独立业务、容量和故障收益;无收益的伪边界合并,有明确所有权但依赖错误的边界则打断环。
- 问题:循环依赖怎样治理?
- 考点:依赖方向。
- 回答思路:确定权威、抽取流程或用事实反转依赖。
- 详细答案:先确认双方各自拥有的事实,把查询他域内部状态改为本地投影或明确接口,把共同流程交给应用层协调;若两边规则总是共同变化,可能应合并边界。
- 进阶追问:引入消息就能解环吗?
- 进阶回答:不能;若双方仍相互等待对方事件才能提交,或事件只是伪装命令,逻辑环依然存在。
- 问题:边界事故第一步查什么?
- 考点:实际运行证据。
- 回答思路:以业务键还原写路径。
- 详细答案:冻结时间窗和受影响业务键,查数据库审计、调用链、消息版本、投影水位和发布记录,找第一个非所有者写入或错误解释事实的位置,而非只看最终异常栈。
- 进阶追问:先回滚可以吗?
- 进阶回答:先确认契约和数据中间态可回切;盲目回滚可能让旧代码读不懂新数据或重复副作用。
8.2 合并拆分触发、迁移路径、架构评审与项目话术
拆分触发来自独立变化、容量热点、故障隔离、安全合规、数据所有权和团队自治的持续证据;合并触发来自频繁联合变更、跨域事务占主导、调用往返无业务价值、团队认知过载和运行收益不足。不能以代码行数或服务数量设统一阈值。决策应进入 ADR(架构决策记录),写目标、非目标、候选、证据、剩余风险、撤销条件、责任人和复审日,复用 架构评审与可逆性。
迁移遵循“先逻辑、后流量、再数据、最后收权”:冻结业务语义和目标所有者;在原系统内建立模块接口与单写者;引入 ACL(防腐层)或 OHS(开放主机服务)并对影子结果;按业务键、租户或仓灰度切流;迁移历史数据并持续对账;撤销旧写权限、旧任务和旧告警。合并也要保留契约适配,先把调用转成本地接口,再迁数据和运行责任,不能直接复制代码后留下两套写路径。
| 决策 | 触发证据 | 迁移保护 | 撤销条件 |
|---|---|---|---|
| 拆分 | 独立变化、容量或故障收益 | 单写者、影子校验、灰度 | 收益不足、差异超窗 |
| 合并 | 联合变化和运行成本长期占优 | 契约适配、数据水位 | 模块边界再次被穿透 |
| 暂缓 | 证据不足且最后时刻未到 | 模块化、可回放事实 | 容量或合规逼近 |
| 重新划界 | 同词异义或责任冲突 | 语言重命名、事实迁移 | 新边界仍频繁互写 |
sequenceDiagram
participant 评 as 架构评审
participant 旧 as 旧边界
participant 新 as 新边界
participant 数 as 数据校验
participant 运 as 运行责任人
评->>旧: 冻结语义、写者与撤销条件
旧->>新: 建立兼容接口和影子流量
新->>数: 按业务键输出影子结果
数-->>评: 返回差异、版本和水位
alt 达到批准阈值
评->>新: 分批切换权威流量
运->>旧: 撤销写权限与旧任务
数-->>运: 连续观察无新增差异
else 差异超窗或恢复失败
评->>旧: 保持原权威并停止扩面
新->>数: 修复后重新影子验证
end图解读:节点覆盖评审、新旧边界、数据校验和运行责任;箭头从语义冻结到影子、切流和收权;前提是旧权威始终明确;失败路径停止扩面而不是双主运行;结论是迁移成功由写权和责任收口证明,不由新服务上线证明。
数据演绎 14:拆分收益的敏感性
E3(演练证据):候选拆分每月节省联合发布等待 12 人日、故障损失期望 8 人日,新增契约、值班和对账成本 14 人日,净收益演练为 12+8-14=6 人日。若故障收益高估 50%,净收益变为 12+4-14=2;若新增成本再升 3 人日则为负。结论应是小范围验证并设置回迁条件,而不是宣称拆分必然先进。
热门面试题
- 问题:什么情况下应该拆分边界?
- 考点:证据驱动拆分。
- 回答思路:列变化、容量、故障、安全和团队收益。
- 详细答案:当某能力有稳定独立语言和所有权,并在发布、伸缩、故障隔离、安全或团队自治上持续获益,且收益覆盖契约、数据、观测和值班成本时拆分。先模块化验证比直接建服务更可逆。
- 进阶追问:表很大是否应拆?
- 进阶回答:表大小是存储和容量信号,不自动决定业务边界;可先分区、归档或读写隔离。
- 问题:什么情况下应该合并服务?
- 考点:回迁不是失败。
- 回答思路:看联合变化、跨域协调和运行收益。
- 详细答案:若服务长期共同发布、互相同步调用、事务和故障无法隔离,且独立容量收益很小,合并为清晰模块能降低总成本。合并后仍保留内部所有权和测试边界。
- 进阶追问:合并会不会退化成大泥球?
- 进阶回答:会有风险,所以只合并部署和运行责任,不取消模块依赖规则、单写者、契约测试与复审触发。
- 问题:如何在面试中讲边界重构项目?
- 考点:架构师项目话术。
- 回答思路:按背景、证据、候选、迁移、失败和结果边界回答。
- 详细答案:先讲同词异义、多写者或发布等待等问题及证据,再说明为何选择拆分、合并或暂缓;展开单写者、影子校验、灰度、对账和撤销,最后按 E1(源码与可复现证据)至 E0(待核对)限制结果陈述。
- 进阶追问:没有真实指标怎样讲?
- 进阶回答:明确说是 E2(已有材料映射)候选方案和 E3(演练证据)计算,并列需要补的发布、流量和事故证据,不编提升数字。
8.3 知识节收口(非知识型)
以上 14 个知识型三级标题各有知识标记、三道六字段题、图、表和数据演绎。以下综合题只训练 3 至 5 分钟架构口述,不再计入知识型三级标题。
9. 综合面试题库
问题(综合题 1):拿到一个新业务,你怎样从零判断领域和服务边界?
- 口述答案:我不会先画微服务框图,而会先声明事实边界和决策顺序。第一步和业务责任人确认目标、非目标、主要角色、成功事件、不可接受损失以及 E1(源码与可复现证据)、E2(已有材料映射)、E3(演练证据)、E0(待核对)各自能支持什么结论。第二步沿价值流收集业务事件、命令、规则和异常,识别组织长期需要具备的业务能力,再按差异化价值划核心、支撑和通用 Subdomain(子域)。第三步专门找同词异义、不同状态机、不同变化原因和不同责任人,以此提出 Bounded Context(限界上下文)候选,并为每个边界写 Ubiquitous Language(通用语言)、权威事实、局部不变量和禁止越界事项。第四步才判断 Aggregate(聚合),只把必须在一次本地提交内成立的规则放在一起,跨域规则转成流程状态、补偿和对账。第五步画
Context Map(上下文映射),明确上游下游、ACL(防腐层)、OHS(开放主机服务)、Published Language(发布语言)及查询、命令、事件的使用边界。最后独立评估团队认知负荷、发布频率、容量热点、故障隔离和安全要求,决定逻辑边界是否需要物理服务化。评审必须包含维持模块化单体现状的候选,定义独立发布、差异年龄、跨团队等待和恢复时间等信号;收益没有证据就先模块化验证。这样能避免把菜单、表或组织图直接变成服务,也能在边界不成立时保留合并和回迁路径。 - 追问一:第一张图画什么?
- 直答一:先画业务事件、决策点和责任,不画技术组件。
- 追问二:什么时候决定数据库?
- 直答二:数据所有权和不变量确定后,再按权限、容量、恢复和合规决定同库隔离或独库。
- 追问三:如何避免分析无限进行?
- 直答三:写最后负责时刻、未知证据和小范围验证,按风险在截止日作有限承诺。
- 详细章节:业务语义到边界
- 口述答案:我不会先画微服务框图,而会先声明事实边界和决策顺序。第一步和业务责任人确认目标、非目标、主要角色、成功事件、不可接受损失以及 E1(源码与可复现证据)、E2(已有材料映射)、E3(演练证据)、E0(待核对)各自能支持什么结论。第二步沿价值流收集业务事件、命令、规则和异常,识别组织长期需要具备的业务能力,再按差异化价值划核心、支撑和通用 Subdomain(子域)。第三步专门找同词异义、不同状态机、不同变化原因和不同责任人,以此提出 Bounded Context(限界上下文)候选,并为每个边界写 Ubiquitous Language(通用语言)、权威事实、局部不变量和禁止越界事项。第四步才判断 Aggregate(聚合),只把必须在一次本地提交内成立的规则放在一起,跨域规则转成流程状态、补偿和对账。第五步画
问题(综合题 2):业务能力、Subdomain(子域)和 Bounded Context(限界上下文)如何落到一个实际项目?
- 口述答案:以跨境订单为例,我先把“客户下单并最终收货”拆成价值流,而不是按现有页面拆模块。交易接受购买意图、定价并维护商业状态,是业务能力;库存承诺、支付确认、履约编排和轨迹接入也是长期能力。再看差异化:库存防超卖和跨境履约规则可能构成核心或重要支撑 Subdomain(子域),通知、文件等更接近通用能力,但分类要由企业竞争力和变化投入决定,不能套模板。随后检查语言:交易的“订单完成”是商业里程碑,支付的“成功”必须有渠道证据,履约的“完成”是包裹交付;这些词若被公共状态枚举统一,就会丢失各自恢复动作,因此应形成局部 Ubiquitous Language(通用语言)和 Bounded Context(限界上下文)。每个上下文写出权威事实和不变量:库存守数量,支付守金额与请求唯一,履约守作业和轨迹不倒退,交易只汇总旅程。跨域只交换预占标识、支付单标识、履约标识和版本化事实,不传对象图。然后用变化数据验证:若库存规则常独立变化、支付有独立合规和值班,就提高物理隔离优先级;若交易与某支撑能力长期共同发布且团队很小,可先保持同部署模块。E2(已有材料映射)只支持这是项目候选讲法,真实边界还需源码依赖、发布记录、事故和团队访谈补成 E1(源码与可复现证据)。评审产物还要为每个候选边界指定负责人、上下游和禁止事项,并用三次代表性变更回放检查语言是否稳定;若每次都要跨边界改模型,就重新划界而不是继续加接口。
- 追问一:一个 Subdomain(子域)能有多个上下文吗?
- 直答一:可以,地区规则、生命周期或团队语言显著不同都可能形成多个模型边界。
- 追问二:上下文能跨团队吗?
- 直答二:短期可以,但共同所有权会增加协调,需明确单一决策和运行责任。
- 追问三:通用子域是否都应采购?
- 直答三:不是,还要看集成、退出、合规和总拥有成本。
- 详细章节:DDD(领域驱动设计)问题空间
问题(综合题 3):Aggregate(聚合)边界和服务边界为什么不能一一对应?
- 口述答案:Aggregate(聚合)解决的是局部一致性和并发修改入口,服务边界解决的是业务能力所有权及独立发布、伸缩、故障和团队运行,因此粒度和决策维度不同。我的做法是先列不变量:库存单元的初始量、入库、预占、占用、释放和修正必须守恒,适合在一个 Aggregate(聚合)内由 Aggregate Root(聚合根)校验版本并原子写流水;商品描述、订单商业状态、支付渠道事实和履约任务不需要与库存同次提交,只保存稳定标识并通过流程协调。若每个 Aggregate(聚合)都建服务,订单行、库存单元、预占、履约任务之间会出现大量网络调用,原本本地应用服务可协调的动作被推成分布式事务,契约、观测和值班成本迅速上升。反过来,一个 Bounded Context(限界上下文)也可能因查询热点拆成写服务和读投影两个部署单元,但仍共享一套领域语言和所有权。评审时我分别检查聚合的锁竞争、加载范围和不变量完整性,再检查服务的变化频率、容量曲线、故障隔离和团队能力;只有两组证据同时支持,才选择相应部署。若热点只是单表访问,可以先做索引、分区或同上下文内并行,不必改变业务边界。迁移后要验证非法状态为零、跨聚合流程可恢复、独立发布真实成立,并保留模块合并或回迁条件。同时记录跨聚合协调的最长处理中时间和失败责任;如果缩小聚合后异常状态长期无法收敛,说明局部性能收益正在透支业务恢复能力,应扩大原子边界或重构流程。
- 追问一:聚合越小越好吗?
- 直答一:不是,过小会把必须原子成立的不变量变成跨域协调。
- 追问二:聚合间能否直接对象引用?
- 直答二:通常只保存标识,由应用服务或领域事实协调,避免生命周期绑定。
- 追问三:查询需要组合多个聚合怎么办?
- 直答三:使用场景 Read Model(读模型),关键写前仍回权威聚合校验。
- 详细章节:聚合与边界决策
问题(综合题 4):如何在共享数据库里真正落实 Single Writer Principle(单写者原则)?
- 口述答案:我先澄清 Single Writer Principle(单写者原则)是业务事实的逻辑裁决权,不是只有一个线程、实例或数据库节点。即使暂时共享数据库,也能通过所有权账本、模块接口、账号权限、数据库约束和审计把写权收口。第一步盘点应用、批处理、报表、运维脚本和人工账号对目标表的全部写入口,按业务键抽样验证,不能只搜主仓库代码。第二步确定谁定义语义、维护不变量、处理修复和接受审计,例如库存域拥有预占流水,交易域只能提交预占或释放 Command(命令)。第三步在所有者内建立唯一写接口,要求稳定业务键、期望版本和合法状态转换,同一事务写状态与审计;其他模块先通过适配层调用。第四步逐个迁移旁路写,迁移期旧路径只允许影子比对,不允许两个权威互相覆盖。第五步撤销非所有者数据库写权限,对紧急修复提供生成修正流水的受审计工具。运行信号包括按账号的写次数、未知写源、版本冲突、差异年龄和修复队列。若发现遗留任务无法迁移,保持旧所有者为权威并暂停扩面,而不是长期双主。完成标准是新写流量、权限、契约、告警、对账和人员责任都指向同一所有者;E1(源码与可复现证据)应来自权限清单、审计日志和故障演练,而不是架构图上的数据库框。若账号层暂时无法细分,就在数据库触发审计、应用写入口和发布门禁三处补偿控制,并把完成权限收缩列为有截止日的风险,不能把临时控制当最终边界。
- 追问一:多实例如何保证单写者?
- 直答一:所有实例执行同一领域规则,并按业务键、版本和数据库约束裁决。
- 追问二:紧急修数是否破例?
- 直答二:可走审批,但仍由所有者脚本生成修正流水、预演并复核,不能无痕更新。
- 追问三:共享读账号要撤吗?
- 直答三:按最小权限保留必要读取,禁止依赖未发布内部列,长期应转接口或投影。
- 详细章节:数据所有权与单写者
问题(综合题 5):两个服务共同写一张订单表,怎样迁移到清晰所有权?
- 口述答案:我不会直接拆表或开启双写,而会先裁决“共同写的是否真是同一个事实”。例如客户资料域维护可变联系地址,交易域维护下单时地址快照,履约域维护实际交付地址和变更审批;字段相似但生命周期不同,应先重命名语义。接着冻结目标模型和目标所有者,按数据库审计列出每个列、状态和写来源,标记规则、批处理、报表和人工入口。迁移采用扩展、迁移、收缩:先在旧库新增明确字段、版本和所有者命令接口,所有新业务通过目标所有者写;旧服务改为调用命令或保存自己的
Value Object(值对象)快照。历史数据按来源、时间和业务事件回填,无法判断的记录进入 E0(待核对)队列,不用最新值覆盖历史。迁移期可以影子写新投影,但旧表保持唯一权威,按业务键比较状态、版本和审计来源;达到水位后切换读取,再撤销旧账号写权限、停旧任务和告警。失败路径包括旧批处理继续覆盖、双写乱序、回滚后旧代码不识别新字段和报表依赖内部列,所以每阶段都要可回切并保留原始证据。完成后用并发修改地址与发货、历史订单不可污染、旁路写为零和差异对账验证。若真实调用和数据量缺失,所有时长与比例只标 E3(演练证据),不能说线上迁移已经成功。迁移完成后仍要保留一个观察期,持续抽样新旧语义、校验修复脚本和回滚演练;任何旧写源重新出现都自动阻断发布并重新打开所有权迁移任务。同时确认客服、财务和数据团队已切换到新语义,避免技术写权收口后,人工流程仍按旧字段继续制造分叉。 - 追问一:能否先拆成两张表?
- 直答一:可作为扩展步骤,但语义和写权未裁决前,拆表只会制造两份冲突事实。
- 追问二:历史脏数据谁负责?
- 直答二:目标所有者定义裁决规则,数据和业务责任人共同确认,证据不足转人工。
- 追问三:回滚时以哪侧为准?
- 直答三:始终以迁移计划声明的当前权威为准,影子侧不能反向覆盖。
- 详细章节:遗留所有权迁移
- 口述答案:我不会直接拆表或开启双写,而会先裁决“共同写的是否真是同一个事实”。例如客户资料域维护可变联系地址,交易域维护下单时地址快照,履约域维护实际交付地址和变更审批;字段相似但生命周期不同,应先重命名语义。接着冻结目标模型和目标所有者,按数据库审计列出每个列、状态和写来源,标记规则、批处理、报表和人工入口。迁移采用扩展、迁移、收缩:先在旧库新增明确字段、版本和所有者命令接口,所有新业务通过目标所有者写;旧服务改为调用命令或保存自己的
问题(综合题 6):请完整拆分交易、支付、库存、履约四域并说明协作路径。
- 口述答案:我的拆分依据是四类不变量和证据来源,而不是把订单流程切成四段。交易域拥有购买意图、订单行、价格快照、取消资格和商业状态,保证同一意图不重复成单且状态合法推进;库存域拥有库存单元、预占、占用、释放和修正流水,保证数量守恒与确认不超权威可用量;支付域拥有支付单、商户请求号、金额币种、渠道交易和未知状态,保证不重复扣款、不猜渠道结果;履约域拥有履约单、仓配任务、面单与轨迹,保证作业幂等和状态不倒退。正常路径是交易创建意图,以订单行动作号请求库存预占,拿到预占标识后以稳定请求号创建支付;支付确认事实到达后,交易或流程所有者确认预占有效并创建履约。每域只返回标识、裁决和版本化事实,不让交易直接改库存余额、支付成功或轨迹。失败时,库存拒绝则交易终止;支付超时保持未知并由支付域查单;支付成功但预占失效时由流程所有者决定重新预占或创建退款,具体数量与资金动作仍由所属域执行;履约已拣货则不能简单取消,要走业务补偿。查询页面使用跨域 Read Model(读模型),但发货、退款等关键动作回权威域校验。最终用库存流水、渠道流水、支付单、履约作业和交易状态做四方对账,并为未知、补偿和人工记录设置最大年龄。四个逻辑域可先同进程部署,是否独立服务另看团队和运行证据。四域之间还要约定契约废弃、值班升级和人工裁决边界;否则正常流程看似清楚,真正遇到渠道未知、仓内作业或长期退款时仍会出现责任真空。
- 追问一:谁是端到端流程所有者?
- 直答一:通常是交易域应用层或专门流程组件,但它不获得其他域的数据写权。
- 追问二:支付成功是否直接触发发货?
- 直答二:还需校验订单资格、库存预占和风控等权威条件,不能只看一个事件。
- 追问三:如何展示一个订单状态?
- 直答三:用旅程读模型汇总里程碑和处理中原因,不覆盖各域权威状态。
- 详细章节:四域完整拆分
问题(综合题 7):跨域查询如何避免联表、级联调用和陈旧数据误导?
- 口述答案:我先按查询用途分级。写前裁决、资金确认、库存确认和权限判断必须访问权威所有者,不能由缓存或读模型替代;用户列表、搜索、运营报表和跨域旅程适合 Read Model(读模型),因为它们更重视组合效率并可接受明确的新鲜度窗口。对于轻量、低频且依赖少的详情,可做 API(应用程序接口) Composition(接口聚合),但聚合层要传播截止时间、限制并发和部分结果,不能无界串行调用。对于高频列表,消费各域版本化 Event(事件)构建本地投影,保存来源业务键、源版本、投影时间和删除权限状态;重复和旧版本忽略,无法识别的契约进入隔离。返回结果应携带更新时间或新鲜度等级,超窗时回源、降级或禁止关键按钮。绝不允许读模型反向成为写权威:用户从搜索结果点击扣库存,最终仍向库存域发 Command(命令)并重新校验。排障时同时看源事件水位、最老投影年龄、业务键差异、删除和权限传播,不用查询成功率掩盖陈旧。重建能力也要验证:从权威事实全量构建后追平增量,按业务键核对关键字段和版本。若多个服务临时共享库联表,应把它登记为过渡依赖,限制只读账号和列,设迁移截止日;否则一个表结构变更会重新绑定所有边界。对外响应还要区分“权威已确认”“投影处理中”和“数据可能陈旧”,前端及调用方按级别限制动作;这比返回一个无来源的统一状态更能阻止陈旧数据越权。
- 追问一:详情查询能同步调用四个域吗?
- 直答一:可在明确预算和降级下使用,但高频关键路径更适合投影,写动作仍回权威域。
- 追问二:读模型多久延迟可接受?
- 直答二:由用户动作和业务损失决定,写成分位新鲜度与超窗动作,不能统一拍秒数。
- 追问三:投影错了怎样恢复?
- 直答三:停止关键使用,从权威快照加增量重建,按业务键和版本对账后再放量。
- 详细章节:跨服务查询与读模型
问题(综合题 8):如何判断一次跨域交互应该是 Query(查询)、Command(命令)还是 Event(事件)?
- 口述答案:判断依据是业务语义和责任,不是使用超文本接口还是消息队列。Query(查询)表达“告诉我当前知道什么”,接收者不应产生业务状态变化;Command(命令)表达“请拥有该事实的一方尝试执行动作”,接收者必须校验授权、版本和不变量,可接受、拒绝或返回可查询的处理中;Event(事件)表达“某个事实已经发生”,生产者拥有语义,消费者自行决定反应,不能要求某个消费者必须完成特定动作。以库存为例,展示可售提示是 Query(查询),确认预占是 Command(命令),“库存已预占”是 Event(事件)。即使“扣库存消息”通过异步通道发送,它仍是 Command(命令),需要目标、幂等键和结果状态;即使同步查询返回支付已确认,它也只是读取支付权威事实。设计时为命令定义业务键、期望版本、截止时间、拒绝原因和查证入口;为事件定义事件标识、聚合标识、业务版本、发生时间、契约版本和最小必要字段;为查询定义新鲜度、权限和回源边界。失败反例包括把数据库行镜像成事件、让生产者为每个消费者定制事件、消费者收到事件后无幂等地产生外部副作用,以及用陈旧查询直接做资金或库存承诺。评审最终检查每次交互失败后谁拥有下一步、能否重放、能否查证、是否会因重命名而改变语义。我还会把交互类型写入接口评审和契约名称,持续扫描把事件改成命令、把查询夹带业务写入等漂移;语义变化需要重新评审,而不是只改传输通道。
- 追问一:事件可以包含建议动作吗?
- 直答一:可以附上下文,但不能把必须执行的定向请求伪装成事实广播。
- 追问二:命令是否必须同步?
- 直答二:不必须,异步命令仍要有目标、幂等和可查询结果。
- 追问三:查询能触发缓存加载吗?
- 直答三:技术副作用可以,但不能改变可观察业务事实,且要控制放大和失败。
- 详细章节:查询、命令与事件
问题(综合题 9):同步和异步集成应怎样做架构评审?
- 口述答案:我先把选择转成用户承诺和失败责任。若当前交互必须得到权威裁决,例如库存最终确认,且依赖能在端到端截止时间内稳定响应,可以同步;若允许“已受理”、后续动作耗时长、需要隔离峰值或一个事实有多个消费者,可以异步。同步评审要列依赖尾延迟、并发槽位、超时预算、重试上限、幂等和超时未知处理,不能把超时当明确失败。异步评审要列持久化受理点、业务键、重复乱序、积压容量、最老消息年龄、净清理能力、死信、查证和人工出口,不能只说削峰解耦。常用组合是入口同步保存意图和 Outbox(发件箱),立即返回查询标识,后续异步履约;或同步完成权威校验,再异步更新搜索和报表投影。候选比较必须包含维持同步并限流、异步受理、批处理和混合方案,按用户时限、外部不确定性、恢复目标、团队运行能力和成本淘汰。以跨境承运商为例,接口 99 分位超过入口预算时,异步能缩短受理,但总完成仍受承运商限制;必须展示处理中并按原请求号查单。上线验证不只看接口成功率,还看最终完成分位、未知年龄、积压净清理、重复外部副作用和业务差异。任何没有运行数据的吞吐只标 E3(演练证据),并设置回到同步受限模式或暂停低价值任务的撤销条件。容量演练还需注入消费者停摆、下游限流和恢复流量,验证净清理能力不会压垮权威库;若恢复时间超过用户承诺,异步方案同样应被拒绝或缩小范围。评审结论要明确入口受理和最终完成是两个服务目标,分别配置告警与业务负责人。
- 追问一:异步是否一定降低耦合?
- 直答一:降低运行时同时在线耦合,但增加契约、时间和运维耦合。
- 追问二:超时后能否立即重试?
- 直答二:先按原业务键查证;确认幂等和未执行后才有限重试。
- 追问三:队列清零是否代表恢复?
- 直答三:不代表,还要核对最终状态、外部副作用、差异和用户可见时间。
- 详细章节:同步异步选择
问题(综合题 10):接入不可控承运商时,怎样设计 ACL(防腐层)和上下文映射?
- 口述答案:我先确认权力关系:承运商协议、状态和限额由外部控制,履约域不能要求其使用内部模型,因此履约域应拥有 ACL(防腐层),而不是让交易、客服、报表各自适配。ACL(防腐层)分协议适配和语义翻译两层:协议层处理鉴权、签名、限频、超时和原始响应;语义层把承运商状态映射为履约域里程碑,保存承运商、映射版本、原始状态、发生时间和证据摘要。遇到新状态、时间倒退或语义冲突时进入未知隔离,不默认映射成功或失败。履约域再通过 OHS(开放主机服务)向内部提供创建面单、查询轨迹等稳定能力,并以 Published Language(发布语言)发布“面单已创建”“包裹已交付”等最小事实;交易域只理解这些内部语义,不感知外部码。不同承运商的查询、回调和轮询按稳定请求键与状态版本收敛,超时保持未知并主动查证。评审还要防止 ACL(防腐层)膨胀成万能中台:它只保护履约模型,定价、订单和支付规则仍在各域。版本变更先用历史响应回放,灰度单一承运商或租户,观察未知映射率、轨迹倒退、重复面单和人工积压;失败时回到旧映射并保留新状态原始证据。这样外部变化只影响边界适配,不会污染内部所有消费者。每次映射变更还要标出规则所有者和复审日期;承运商长期不再返回的状态不能直接删除,需先核对历史重放、未完履约单和人工工单中的旧值。映射规则上线后还应抽样核对真实包裹和用户页面,避免技术状态转换正确但业务里程碑错误。
- 追问一:为何不让承运商适配我们的模型?
- 直答一:缺乏控制力时无法依赖对方长期配合,下游应主动保护自己的语义。
- 追问二:未知状态是否直接报错?
- 直答二:保留原事实并隔离查证,对用户展示处理中,不猜终态。
- 追问三:ACL(防腐层)能否共享给多个域?
- 直答三:可共享履约对外能力,但映射所有权仍在履约域,不能承载各域业务规则。
- 详细章节:上下文映射
- 问题(综合题 11):为什么共享表、双写和公共大模型会把系统变成分布式单体?
- 口述答案:这三个做法都绕过了业务边界的显式契约。共享表让多个服务直接解释和修改同一事实,数据库列变成未发布接口;任一状态或模式变化都要求共同发布,数据库故障也成为共同故障域。应用双写试图在两个独立权威源之间制造原子性,但进程崩溃、响应丢失、重试和乱序都会留下分叉,两个接口各自 99.9% 成功也不能证明同一业务键一致。公共大模型则把交易的商业状态、支付的渠道状态、库存数量和履约作业压进同一个 Order(订单)对象,任何局部变化都触发全链依赖,并诱导消费者直接修改不属于自己的字段。结果是服务虽然分进程部署,却不能独立演进、发布、回滚或恢复,既承担网络和消息复杂度,又保留单体耦合。治理顺序不是先拆库,而是先按语义裁决所有权和不变量,为每个事实确定 Single Writer Principle(单写者原则);其他域通过 Command(命令)、Query(查询)、Event(事件)和 Read Model(读模型)协作。共享数据库过渡期用 Schema(模式)、账号和迁移权限隔离;双写只允许一个权威,另一侧作影子并按业务键校验;公共模型收缩为稳定标识和小型
Value Object(值对象)。最后用独立发布、旁路写为零、差异年龄、故障隔离和回滚演练证明解耦,否则只是换了代码目录。治理完成后还要从代码依赖、数据库权限、发布流水和故障演练四面复核;只把公共类复制成四份而不改变写路径与责任,仍然是同一个分布式单体。 - 追问一:共享数据库能否长期存在?
- 直答一:可以,只要所有权、权限、模式迁移和恢复边界清楚,并且独立演进收益不足以支持拆库。
- 追问二:事务型双写工具能解决吗?
- 直答二:只能在满足资源前提时缩小部分窗口,外部副作用、语义冲突和责任仍需处理。
- 追问三:公共工具包也危险吗?
- 直答三:技术工具可共享,携带领域状态和规则的公共包会形成发布耦合。
- 详细章节:共享与双写反例
- 问题(综合题 12):Conway’s Law(康威定律)和 Cognitive Load(认知负荷)怎样影响团队划分?
- 口述答案:我把 Conway’s Law(康威定律)当作可验证约束,不当作组织口号。先沿价值流统计一次需求从提出到上线经过多少团队、等待多少天、哪些接口反复协商,再看事故时告警经过多少次移交、谁能同时理解业务事实、代码、数据和恢复。如果交易与库存 80% 的变化都共同发生,却由两个按技术层划分的团队串行排期,系统会通过共享库和临时接口重新耦合;如果一个团队同时承担支付、库存、搜索、消息和平台全部运行模型,即使边界清楚也可能因 Cognitive Load(认知负荷)过高而失去所有权。目标是 Stream-Aligned Team(价值流对齐团队)能端到端拥有一组相关业务能力,Platform Team(平台团队)提供自助发布、观测、安全和数据护栏,复杂专长按需要协作但不成为永久工单。Inverse Conway Maneuver(逆康威操作)可以主动调整团队促进期望边界,但要小步验证,因为重组本身有学习和人员成本。指标包括跨团队等待占前置时间比例、联合发布次数、事故移交、值班告警、关键知识集中度和团队反馈。若调整后沟通边下降但业务错误上升,说明边界或能力配置有问题;若小团队无法完整分工,可以一人兼多角色,但决策和风险签署仍需清楚。最终不是一个团队一个服务,而是让变化、数据写权和恢复责任尽量在可承受的认知边界内闭环。团队调整后我会用至少一个完整发布周期和一次故障演练复核,避免把短期沟通减少误判为长期收益;人员流动后边界仍能被接手,才说明认知负荷真正可控。
- 追问一:团队应围绕系统还是业务能力?
- 直答一:优先围绕稳定价值流和业务能力,系统只是当前实现载体。
- 追问二:平台团队如何避免垄断?
- 直答二:把能力做成自助产品,公开服务目标、反馈渠道和退出方式,不代替业务团队运行。
- 追问三:组织多久调整一次?
- 直答三:按价值流和认知证据触发复审,避免频繁重组,也不让历史结构永久固化。
- 详细章节:康威定律与认知负荷
- 问题(综合题 13):为什么服务边界和部署边界不等价,项目中怎样分别决策?
- 口述答案:服务边界回答谁拥有一项业务能力、模型和契约,部署边界回答哪些代码需要独立发布、伸缩、隔离和恢复,两者目的不同。项目中我先建立 Bounded Context(限界上下文)、模块依赖和 Single Writer Principle(单写者原则),即使代码仍在同一进程,也禁止跨模块直接改内部表和对象;这使小团队可以先享受本地事务、简单调试和低运行成本。随后收集变化和运行证据:发布是否经常互相阻塞,查询与写入容量曲线是否显著不同,一个模块故障是否需要隔离,安全或合规是否要求独立账号和网络,团队是否能承担独立值班。若交易查询峰值远高于写入,可把同一上下文拆成查询部署和写部署,查询使用 Read Model(读模型),但语义和写权仍归交易域;若两个低变化支撑上下文长期由同一团队维护,也可同部署而保持模块隔离。反例是每个 Aggregate(聚合)一个服务,导致网络跳数和分布式事务爆炸,或认为同进程就可以共享内部模型。拆分候选必须和整体扩容、优化热点、模块化单体比较,计算新增契约、观测、发布、数据迁移和值班成本。上线用独立发布、故障注入、容量曲线和差异对账验证,若隔离和成本收益不成立就回迁部署,同时保留逻辑边界。这样架构可以随证据渐进演进,不把微服务数量当成熟度。决策记录还要写清逻辑边界和物理边界各自的撤销条件,防止一次部署拆分失败被误读为领域建模失败,也防止逻辑边界正确就永久合理化高昂运行成本。
- 追问一:同上下文多个服务会不会重复模型?
- 直答一:可共享领域语义但各部署只保留所需模型,契约和所有权仍需明确。
- 追问二:同部署多个上下文如何隔离?
- 直答二:模块接口、依赖规则、账号权限、测试和迁移所有权共同隔离。
- 追问三:何时回迁?
- 直答三:独立收益长期不足、联合变化和运行成本占优,且可安全迁移数据与流量时回迁。
- 详细章节:服务与部署边界
- 问题(综合题 14):如何设计 API(应用程序接口)和事件的版本、兼容、Schema Evolution(模式演进)与废弃?
- 口述答案:我先把契约视为跨团队的运行承诺,而不是数据结构文件。契约必须同时说明业务语义、字段单位、标识、幂等、错误、顺序、新鲜度、安全和废弃策略。设计变更时先判断向后兼容和向前兼容:新消费者能否读取历史数据,旧消费者能否忽略新字段或未知枚举,滚动期间新旧生产者和消费者能否任意组合。普通新增采用 Expand/Migrate/Contract(扩展/迁移/收缩):先发布可选字段或新端点,不改变旧字段含义;再让新消费者双读或让生产者按同一业务键双版本发送,保存结果差异、消费水位和历史重放结果;所有调用方迁移且观察窗内旧流量归零后,停止旧写,再撤销旧读、权限和路由。字段改义、单位变化或状态机变化属于破坏性语义,应发布新事件类型或主版本,不能靠改文档掩盖。Deprecated(已废弃)阶段要有所有者、调用方清单、截止日、通知和逾期阻断,关键支付补单等长尾即使占比很小也不能忽略。测试同时覆盖新生产者对旧消费者、旧消息对新消费者、未知枚举、字段缺失、重复乱序和回滚。发布后观察解析失败、未知值、双版本差异和旧调用;发现历史重放失败就停止收缩。这样废弃完成由零流量、零权限、零回放依赖和可验证回滚证明,而不是由版本号或代码删除证明。对外契约还应保存代表性请求、响应和旧消息样本,作为每次发布的固定回归输入;没有样本与消费者水位,所谓兼容只能证明编译通过,不能证明业务可重放。
- 追问一:新增枚举值是否兼容?
- 直答一:结构上可能兼容,行为上旧消费者若穷举失败就不兼容,必须测试未知值策略。
- 追问二:何时使用新主版本?
- 直答二:语义、身份或状态规则无法通过可选扩展保持旧承诺时使用。
- 追问三:长期双版本有什么问题?
- 直答三:会形成双写差异、测试矩阵和维护成本,必须有水位与截止日。
- 详细章节:契约演进与废弃
- 问题(综合题 15):领域事件增加字段、改语义和历史重放时,怎样保证边界稳定?
- 口述答案:领域事件由产生事实的 Bounded Context(限界上下文)拥有语义,消费者只拥有自己的处理和投影。生产者首先避免把数据库行直接镜像成 Event(事件),只发布稳定业务标识、业务版本、发生时间和消费者理解事实所需的最小字段,这样内部重构不必扩散。新增字段时默认可选,生产者先扩展,旧消费者应忽略未知字段,新消费者对缺失字段使用有业务含义的默认或进入隔离,不能凭空猜值。字段改名采用双字段和同一来源校验,确认所有消费与历史数据迁移后再收缩;若含义变化,例如“支付成功”从渠道受理改为资金确认,必须发布新的事件类型或主版本,旧事件定义永久保持。历史重放是兼容门禁的重要部分:保存代表性旧消息、最新 Schema(模式)和消费者版本,以旧消息喂新消费者、以新消息喂旧消费者;重放时消费者按事件标识幂等,并用业务版本阻止旧事实覆盖新状态。双发阶段两个版本携带关联标识,避免消费者执行两次外部副作用,并按业务键对比投影。隐私字段按最小化原则,删除或权限变化要有高优先级传播和重建策略。生产者不能为每个消费者定制事件,否则消费者需求会反向污染领域;确需额外数据时,判断是否属于事实语义,或由消费者 Query(查询)权威接口。监控未知 Schema(模式)、解析失败、旧版本水位、重复副作用和重放差异,任何关键差异都阻断废弃。完成收缩后仍保留旧事件样本和迁移决策,确保未来审计能解释同一业务键为何出现两个版本,并能区分合法双发与重复副作用。
- 追问一:事件字段可以删除吗?
- 直答一:可以,但要证明旧消费者、历史重放和归档数据都不再依赖,再按收缩流程删除。
- 追问二:消费者要求内部字段怎么办?
- 直答二:优先提供稳定业务语义或查询接口,不暴露表结构满足一次性需求。
- 追问三:事件顺序如何处理?
- 直答三:携带业务版本并在业务键内裁决,旧版本不得覆盖新状态,跨键不虚构全局顺序。
- 详细章节:领域事件发布边界
- 问题(综合题 16):跨域最终一致的流程所有者、数据所有者、补偿者和对账者怎样分工?
- 口述答案:我会把“最终一致”拆成五种不可互相替代的责任。第一,各数据所有者用本地事务、版本和约束守住局部不变量,例如库存域守数量、支付域守金额与渠道交易唯一。第二,流程所有者记录端到端意图、已完成步骤、下一步、超时和人工升级,但它只能发送 Command(命令),不能直接修改他域数据。第三,产生外部副作用的领域负责稳定请求键、超时查证和幂等,例如支付域对渠道结果未知负责主动查单。第四,补偿动作由被补偿事实的所有者执行:退款是支付域的新业务单,库存释放是库存域的新流水,履约拦截由履约域判断仓内事实;流程所有者只决定何时请求。第五,Reconciliation(对账)责任人定义左右数据集、统一水位、业务键、金额或数量守恒和差异分类,推动自动修复或人工裁决。每种责任都有最大状态年龄、告警和替代人,不能把任务丢给 MQ(消息队列)或一个无主定时器。正常路径通过本地事实和版本化 Event(事件)推进;超时保持处理中,重复按业务键读取既有状态,乱序按业务版本拒绝倒退。补偿失败继续保持独立状态,不删除原事实。恢复完成必须证明局部不变量仍成立、流程终态合理、外部副作用已核对、差异队列清理且用户可见状态正确。具体技术可以是 Outbox(发件箱)、Saga(长事务模式)或事务消息,但选择之前先把责任和失败时限写清,这样组件替换不会让业务责任消失。
- 追问一:流程所有者是否是新的上帝服务?
- 直答一:不是,它只持有流程状态和协调逻辑,不拥有各域实体与不变量。
- 追问二:谁决定人工处理?
- 直答二:流程按证据完整度和风险分级触发,具体事实由所属业务责任人裁决。
- 追问三:对账可以事后再做吗?
- 直答三:不应,对账是日常控制和上线前设计项,事后临时脚本无法证明完整性。
- 详细章节:一致性责任落点
- 问题(综合题 17):支付已成功、库存预占已失效,怎样补偿并对账?
- 口述答案:首先把渠道成功视为不可由内部回滚抹掉的外部资金事实,也不把库存失效简单改回有效。流程所有者按原订单、库存动作号和商户请求号冻结当前状态,阻止重复支付和直接发货;支付域复核签名、商户、金额、币种和渠道交易号,库存域复核预占流水、过期原因、当前可用量和是否已有其他订单占用。若库存仍可重新预占,流程发送同一业务意图下的新预占 Command(命令),库存域用独立动作号和条件更新裁决,成功后继续履约;若不可预占,则流程向支付域创建独立退款单,退款使用稳定退款请求号,状态可为处理中、成功、失败或未知,超时主动查渠道,不能把原支付单改成失败。若履约已经产生拣货或面单,还要由履约域判断拦截、替代仓或继续履约的业务损失,不能只按技术顺序取消。对账至少覆盖渠道流水、支付单、退款单、订单商业状态、库存预占与释放流水、履约作业六类事实,按同一业务键和水位核对金额、币种、数量和状态。自动修复只处理证据唯一的情况,金额不符、重复扣款或已发货进入人工队列,并给最大年龄和责任人。止血指标看新增差异、重复支付、未知退款、失效预占和人工积压;恢复后故障注入支付延迟、库存过期、消息重复和退款超时,证明状态不倒退、数量金额守恒。若没有真实源码和流水,这只能作为 E2(已有材料映射)方案与 E3(演练证据),不能声称生产效果。整个处置由流程所有者维护统一时间线,任何自动动作都记录依据和操作者;用户补偿、客服说明和财务差错处理也纳入完成标准,不能只让技术流水一致。
- 追问一:为什么不直接恢复旧预占?
- 直答一:过期后库存可能已被其他订单合法占用,直接恢复会破坏数量守恒。
- 追问二:退款失败怎么办?
- 直答二:保持退款处理中,查证、有限重试、渠道升级和人工处理,不能删除支付成功事实。
- 追问三:谁通知用户?
- 直答三:流程所有者汇总可解释状态,支付和履约提供权威原因,通知本身要幂等可追踪。
- 详细章节:库存支付补偿机制
- 问题(综合题 18):线上出现边界漂移,你如何从现象定位到根因并止血?
- 口述答案:边界漂移常以“偶发数据被覆盖、一个小改动要全链发布、事故来回转派”出现,我不会先相信静态架构图。第一步冻结事故时间窗、租户、仓、订单号等业务键和用户影响,保存数据库审计、调用链、消息样本、配置版本、发布记录和人工操作。第二步还原实际写路径:哪些应用、批处理和账号修改了目标事实,哪个服务读取了未发布内部列,是否有 Read Model(读模型)反向写权威库。第三步沿业务键串 Command(命令)、本地提交、Event(事件)、投影、补偿和人工修复,找到第一个非所有者写入或错误解释语义的位置,而不是最后报异常的消费者。止血时冻结未知旁路写、撤销高风险账号、暂停循环重试和联合扩面,保留权威流水与处理中状态;不能直接覆盖差异或清消息。随后裁决目标所有者,建立受版本约束的命令入口,其他方改为查询、事件或本地快照;对事故窗口按统一水位对账,修正通过新流水完成。若根因是两个边界长期共同变化和互相写,考虑合并模块;若业务独立但集成错误,则增加 ACL(防腐层)、Published Language(发布语言)和契约测试。验证包括旁路写为零、独立发布、旧账号拒绝、重复乱序注入和全量差异闭环。最后把漂移原因写入 ADR(架构决策记录)和架构适应度函数,使新跨域表访问、循环依赖或无主契约在合并前被门禁发现。复盘还要统计漂移持续时间、首次越界提交和为何门禁未发现,把检测延迟和责任空窗纳入改进,而不是只修最后一次覆盖的数据。
- 追问一:先回滚代码够吗?
- 直答一:不够,已产生的数据、消息和外部副作用仍需按权威事实对账修复。
- 追问二:怎样发现隐蔽旁路写?
- 直答二:结合数据库账号审计、网络访问、任务平台和人工权限,不只依赖代码搜索。
- 追问三:漂移一定要拆服务吗?
- 直答三:不一定,可能通过模块权限收口,也可能说明伪边界应合并。
- 详细章节:边界漂移排障
- 问题(综合题 19):交易依赖库存、库存又查询交易,怎样打破循环依赖?
- 口述答案:我先判断这是技术调用环还是业务责任本就未分开。交易依赖库存的合理部分是请求预占并获取裁决;库存若为了判断“订单是否有效”同步回查交易,说明命令缺少库存裁决所需的稳定语义,或者库存承担了交易规则。第一种修复是由交易在 Command(命令)中携带经过授权的订单意图标识、仓、商品、数量、有效期和期望版本,库存只根据自己的可用量、重复动作和预占规则裁决,不读取交易内部状态。交易取消时发送释放命令,库存以预占标识和版本处理,迟到释放不能给不存在的预占加库存。若库存确需知道少量交易事实,可消费版本化 Event(事件)建立本地投影,但该投影只支持库存自己的决策,旧版本不得覆盖新状态。端到端先后由交易应用层或流程所有者协调,不让两个领域在事务中互相等待。若分析发现几乎每条库存规则都依赖交易定价、资格和状态,而且由同一团队共同变化,可能边界划错,应在模块化单体中合并应用协调或重新划分,不要用消息掩盖逻辑环。迁移先引入新命令并影子比较库存结果,统计回查交易的原因和命中率;完成后阻断库存到交易的同步依赖,故障注入交易不可用,验证已有库存命令仍能独立裁决。指标看循环调用次数、联合发布、超时放大、投影年龄和差异。这样打破的是责任环,而不是简单把同步请求换成异步消息。删除回查前还要压测命令字段缺失、交易延迟和事件乱序,证明库存能在依赖不可用时给出安全拒绝或处理中,而不是悄悄沿用陈旧资格。
- 追问一:命令携带字段会不会复制数据?
- 直答一:会携带必要上下文,但库存只保存自身裁决所需快照,不获得交易事实所有权。
- 追问二:本地投影陈旧怎么办?
- 直答二:定义可接受版本窗口,超窗拒绝或回权威查询,关键规则不凭陈旧值猜测。
- 追问三:事件驱动为何仍可能有环?
- 直答三:若双方提交都等待对方事件,或相互事件只是定向命令,逻辑依赖仍是循环。
- 详细章节:边界与依赖方向
- 问题(综合题 20):如何判断系统是 Distributed Monolith(分布式单体),又该怎样治理?
- 口述答案:判断不看服务数量,而看自治是否真实。数据面检查多个服务是否共享表写入、是否依赖未发布列、是否必须跨库事务;调用面检查关键请求是否经过长同步链、双方是否循环调用、超时和重试是否级联;发布面检查是否固定顺序联合上线、一个契约变化是否全链升级、回滚是否必须整体回滚;运行面检查单点故障是否拖垮用户旅程、告警是否反复移交、每个团队能否独立恢复;组织面检查团队是否仍按前端、后端、数据库等技术层排队。如果这些同时存在,就是承担了分布式复杂度却没有独立演进收益。治理先按业务键和真实流量画依赖图,选一条高价值旅程作为切入,冻结新增跨域表访问和无界同步调用。然后为每个事实确定 Single Writer Principle(单写者原则),非所有者改为命令、查询或投影;用兼容契约打断联合发布,传播截止时间并删除无业务价值的中间跳数。对有独立容量、故障或合规收益的边界真正解耦,补齐观测、对账和值班;对只是代码分层、长期共同变化的伪服务,合并回模块化单体,保留模块接口和数据权限。迁移期不能同时大爆炸重写,要用影子、灰度和可回切水位逐段处理。验收看独立发布比例、共享写归零、关键链跳数、故障隔离、恢复时间和总拥有成本。若指标未改善,依据 ADR(架构决策记录)撤销或回迁,而不是继续拆更多服务掩盖问题。治理过程每一步都保留业务基线和退出开关,避免一次大规模重构同时改变数据、调用、组织和发布,使任何差异都无法归因。
- 追问一:共享基础设施算分布式单体吗?
- 直答一:不自动算,关键是业务发布、数据写权和故障是否被绑定。
- 追问二:合并服务是否丢面子?
- 直答二:不会,基于证据降低总成本是成熟演进,不是架构倒退。
- 追问三:先治理哪一处?
- 直答三:优先处理高损失旅程上的共享写、循环依赖和不可恢复外部副作用。
- 详细章节:分布式单体排障
- 问题(综合题 21):什么证据足以触发一次服务或上下文拆分?
- 口述答案:拆分应由持续证据触发,而不是文件变大、表变大或追求微服务数量。我要求先满足语义前提:候选能力有稳定 Ubiquitous Language(通用语言)、明确所有者、可独立维护的不变量和可发布契约。再看至少一类可量化收益:变化方面,长期与其他模块低共同变更且联合发布造成等待;容量方面,读写或租户热点有独立曲线,整体扩容浪费明显;故障方面,当前模块事故频繁拖垮核心旅程且隔离能降低损失;安全合规方面,需要独立数据驻留、权限或审计;组织方面,一个可承受 Cognitive Load(认知负荷)的团队能端到端运行。候选必须和维持模块化单体、优化数据库、隔离线程池、分区和读模型比较,先淘汰违反数据和恢复硬约束的方案,再计算新增接口、Schema Evolution(模式演进)、消息、对账、观测、值班和迁移成本。实施先在原系统内建立模块边界和 Single Writer Principle(单写者原则),证明规则能独立;随后通过 ACL(防腐层)或 OHS(开放主机服务)建立兼容接口,影子处理同一业务键并对比,按租户、仓或流量灰度,最后迁数据和收权限。撤销条件包括差异超窗、独立发布收益不足、故障隔离未改善或团队运维负荷过高。没有 E1(源码与可复现证据)时只做小范围验证,不把 E3(演练证据)分数包装成必须拆分的结论。评审还要指定拆分后的服务目标、值班和回迁负责人;没有人愿意接手夜间恢复的边界,即使模型很漂亮也不具备自治前提。
- 追问一:代码超过多少行应拆?
- 直答一:没有统一行数阈值,代码大小只提示可理解性问题,不证明业务和运行自治。
- 追问二:团队人数是否决定拆分?
- 直答二:人数是承接能力约束,仍需业务、数据和运行收益共同支持。
- 追问三:先拆数据库还是代码?
- 直答三:先拆语义、模块依赖和写权,再按迁移风险拆数据和部署。
- 详细章节:拆分触发与迁移
- 问题(综合题 22):什么时候应该把两个微服务合并,怎样避免重新变成大泥球?
- 口述答案:合并触发来自“独立边界长期没有产生收益”。我会看两个服务是否大多数变更共同发生、发布必须固定顺序、调用往返只是传递内部状态、跨服务事务和对账占主要复杂度、故障无法隔离、团队也无法分别值班;同时检查它们是否使用同一 Ubiquitous Language(通用语言)和同一变化原因。如果这些信号持续存在,而独立容量、安全或合规收益很小,合并部署和运行责任可能更合理。合并不是把代码复制到一个目录并开放所有表,而是先确定模块边界和数据所有权:保留各自 Aggregate(聚合)、应用接口、依赖方向、账号或 Schema(模式)规则,让跨服务契约先变成本地端口;再冻结流量水位,停止外部调用,迁移数据或保留同库隔离,撤销旧服务写权限、任务、路由、告警和凭据。原有 Event(事件)若仍服务外部消费者,继续作为 Published Language(发布语言),不能因内部合并破坏别人。测试覆盖模块依赖、状态机、并发不变量和历史重放;运行中观察发布前置时间、故障恢复、资源成本和模块越界。为防止大泥球,建立架构适应度函数:禁止跨模块直接写表、禁止循环依赖、领域状态不得进入公共包,任何边界变化必须有 ADR(架构决策记录)。如果合并后某模块再次出现独立容量或合规需求,逻辑边界使它可重新拆出。回迁是降低总成本的可逆决策,不应被服务数量目标阻止。
- 追问一:合并后还需要契约测试吗?
- 直答一:需要,模块端口和对外发布契约仍需测试,避免内部便利破坏边界。
- 追问二:数据一定要合一吗?
- 直答二:不一定,可先同进程不同 Schema(模式),按恢复和成本决定是否物理合并。
- 追问三:谁批准合并?
- 直答三:业务、数据、运行和团队责任人共同评审,风险接受者对迁移与回滚签署。
- 详细章节:合并与回迁条件
- 问题(综合题 23):从共享数据库迁移到每域所有权,怎样设计可回滚路径?
- 口述答案:我把迁移拆成语义、写权、读取、历史数据和运行责任五条线,始终只保留一个当前权威。首先冻结目标业务术语、字段生命周期和不变量,盘点所有应用、批处理、报表和人工账号,确定每类事实属于哪个 Bounded Context(限界上下文)。第二步在原库中先做逻辑隔离:按模块建立命令接口、版本字段、审计流水、Schema(模式)和最小权限,非所有者的新写统一经过目标所有者。第三步为新边界建立 ACL(防腐层)或 OHS(开放主机服务),将同一业务键送入影子路径;影子侧只记录结果,不对外产生权威副作用,持续比较状态、版本、金额、数量和删除。第四步按租户、仓或时间分片切读,读结果超窗可回旧权威;再分批切写,每一批都有冻结水位和回切开关。第五步历史数据从一致快照加增量位置迁移,无法确定来源的记录不自动覆盖,进入人工裁决。达到调用归零、差异归零、历史重放通过和观察窗稳定后,才撤销旧写权限、停任务、删路由和旧告警。回滚不是把代码版本退回,而是确认旧权威仍接收必要增量、旧 Schema(模式)能读新中间态、已发生外部副作用不会重复;若已切写,回滚需按计划反向迁移水位。整个过程监控旁路写、影子差异、复制年龄、未知记录和人工积压。任何阶段差异超阈值就停止扩面,修复后重放,不进入双主。每次切换前还要抽查旧消费者、导出和运维脚本,确认它们能处理当前中间态;迁移记录保存快照位置、切流批次和责任签署,事故时才能精确回到批准水位。
- 追问一:迁移期能否双写两库?
- 直答一:可影子写,但必须声明单一权威,另一侧不能反向裁决,且要按业务键校验。
- 追问二:何时删除旧列?
- 直答二:旧读写、历史重放、回滚和报表依赖全部归零并过观察窗后再收缩。
- 追问三:快照期间还有新写怎么办?
- 直答三:记录增量位置,先全量再追平增量,切换前校验水位一致。
- 详细章节:每服务一库与共享库边界
- 问题(综合题 24):怎样把 WMS(仓储管理系统)库存防超卖讲成边界与所有权项目话术?
- 口述答案:我会先说明证据边界:WMS(仓储管理系统)和库存防超卖与简历材料有关,属于 E2(已有材料映射);没有源码、数据库约束和运行记录时,不声称具体方案已上线或提升比例。业务背景是促销或交班热点下,交易、库存和仓内作业对“库存”含义不同,若共同写一张订单库存表,会出现超卖、悬挂预占和修复甩锅。我的边界设计让库存域拥有库存单元、预占、占用、释放和修正流水,核心不变量是初始量加流入减预占、占用和流出后守恒,最终确认不能超过权威可用量;交易域只拥有购买意图和商业状态,通过订单行动作号发送预占 Command(命令),列表可以读缓存或 Read Model(读模型),最终确认必须回库存 Aggregate Root(聚合根)做条件裁决。履约域收到预占和支付里程碑后创建拣货任务,已拣货属于新的仓内事实,不能由交易直接回滚。跨域采用单写者、版本化 Event(事件)和对账,缓存只允许错拒绝保护,不能直接给出错误成功。失败路径包括重复请求、释放早于预占、支付超时、预占过期、仓内已作业和旁路修数;每种都以业务键、版本和新修正流水处理。迁移从审计共享表写者开始,先收口库存命令,再影子比对和撤权限。验收看负库存、重复确认、差异年龄、最老预占、仓内作业冲突和恢复时间;真实结果仍需 E1(源码与可复现证据)补齐。项目话术最后主动给出剩余风险,例如热点错拒绝和人工修正延迟,并说明监测与撤销阈值;这比把方案讲成绝对不超卖更符合真实架构边界。
- 追问一:为什么缓存不能作为库存权威?
- 直答一:缓存丢失、过期和复制窗口难以承载完整审计,最终承诺要回权威约束。
- 追问二:热点库存源库扛不住怎么办?
- 直答二:先限流、热点隔离、排队和错拒绝闸门,再用实测决定分区或扩容,不放松不变量。
- 追问三:怎样表达成果?
- 直答三:有运行证据才讲口径与窗口;没有就讲候选、验证、风险和待核对项。
- 详细章节:WMS(仓储管理系统)库存项目
- 问题(综合题 25):跨境物流中交易、履约和承运商边界怎样设计并处理未知结果?
- 口述答案:跨境物流的核心不是多接几个接口,而是把内部承诺与外部不可控事实分开。交易域拥有订单商业状态,只能请求履约,不能直接保存承运商内部状态;履约域拥有履约单、发运意图、面单、轨迹里程碑和异常处理,是内部 OHS(开放主机服务)提供者;每家承运商通过履约域 ACL(防腐层)接入,外部状态码、时间和错误先按映射版本翻译,未知值隔离并保留原始摘要。创建面单使用稳定业务请求号,若同步响应超时,只能把履约单保持未知并复用原号主动查询,不能换号重下;回调、轮询和人工导入按承运商事实可信度与业务版本收敛,旧轨迹不覆盖新状态。履约域通过 Published Language(发布语言)向交易发布“已受理”“面单已创建”“已交付”等稳定里程碑,交易 Read Model(读模型)展示旅程,但取消或退款仍回各权威域裁决。第三方慢或限频时,入口同步保存意图后异步处理,按承运商隔离队列和并发,最老任务年龄、未知状态、重复面单、轨迹倒退和人工积压是关键指标;恢复不能只清队列,还要核对承运商面单、费用、内部履约单和用户状态。迁移新承运商或新映射先用历史响应回放,再按租户灰度;未知映射上升或重复副作用触发回滚。E2(已有材料映射)支持该项目话术,真实限额、时延和事故结果必须用 E1(源码与可复现证据)核对。此外按承运商保存契约版本、数据驻留和人工升级联系人,某一家故障只隔离其新请求与低优先级轮询,不应拖垮其他承运商和核心订单查询。
- 追问一:回调和轮询哪个更权威?
- 直答一:按签名、查询证据和状态版本裁决,不按到达通道绝对优先。
- 追问二:承运商不支持幂等怎么办?
- 直答二:本地稳定请求号、调用前后查证、限制重试和人工处理未知,不能盲重下。
- 追问三:轨迹可以最终一致多久?
- 直答三:由客服、履约动作和用户承诺决定,写成分位新鲜度与超窗升级。
- 详细章节:跨境物流恢复
- 问题(综合题 26):请完整讲述一次边界、组织、数据与集成架构评审。
- 口述答案:我会先做准入,要求方案写清业务目标、非目标、角色、失败损失、量级和 E1(源码与可复现证据)至 E0(待核对)事实等级。评审第一轮只看语义:业务能力和 Subdomain(子域)如何划分,Bounded Context(限界上下文)内的 Ubiquitous Language(通用语言)、权威事实和不变量是否一致,Aggregate(聚合)是否只包住必须本地原子的规则。第二轮看数据:每项事实谁定义、谁写、谁修复和审计,Single Writer Principle(单写者原则)如何由接口、权限、约束和流水证明,共享表、双写和公共大模型是否被淘汰。第三轮看协作:每条跨域边是 Query(查询)、Command(命令)还是 Event(事件),同步或异步的用户承诺、超时未知、重复乱序、积压和人工出口是否明确;
Context Map(上下文映射)是否选择合适的 ACL(防腐层)、OHS(开放主机服务)和 Published Language(发布语言)。第四轮看组织和运行:Conway’s Law(康威定律)下团队能否端到端拥有边界,Cognitive Load(认知负荷)是否可承受,服务边界为何需要或不需要独立部署。第五轮看演进:Schema Evolution(模式演进)是否采用扩展、迁移、收缩,补偿、Reconciliation(对账)、废弃和迁移责任是否有水位、时限和撤销条件。最后用反例攻击支付未知、库存失效、承运商新状态、循环依赖、旧消费者和回滚失败,结论只能是批准、条件批准、拒绝或退回。上线后以独立发布、差异年龄、业务守恒、恢复时间、跨团队等待和总拥有成本复审;证据不支持时允许合并、回迁或重新划界。完整评审的价值不是画出永久正确的边界,而是让每个承诺可验证、每个失败有人负责、每次变化能兼容、每个错误能安全退出。 - 追问一:评审最容易漏什么?
- 直答一:常漏写权迁移、废弃长尾、补偿失败和组织运行责任,只看正常调用图。
- 追问二:如何避免流程过重?
- 直答二:按失败损失和可逆性分级,低风险模块变更轻量记录,高风险跨域迁移完整评审。
- 追问三:架构师价值体现在哪里?
- 直答三:连接业务损失、模型语义、数据责任、组织成本、运行恢复和可逆演进,而非堆组件名。
- 详细章节:架构评审与可逆性
10. 正式图形、复习清单与自检
sequenceDiagram
participant 业 as 业务责任人
participant 架 as 架构评审
participant 数 as 数据所有者
participant 团 as 团队与运行责任人
participant 迁 as 迁移控制
业->>架: 提交目标、不变量和失败损失
架->>数: 核对单写者、契约和对账责任
架->>团: 核对沟通路径、认知负荷和部署收益
alt 边界证据充分且迁移可撤销
架->>迁: 批准影子、灰度与水位校验
迁-->>架: 返回差异、恢复和成本证据
架-->>业: 扩面、收缩或复审
else 语义冲突、责任缺失或回滚不可证
架-->>业: 退回补证、合并或重新划界
end图解读:节点覆盖业务、评审、数据、团队和迁移五类责任;箭头把批准与运行证据相连;前提是边界、写权、契约和撤销可验证;失败路径允许合并与重新划界;结论是架构边界是持续复审的责任系统,不是一次性组织图。

正式 PlantUML(统一建模语言)图使用原生时序语法,不依赖 Graphviz(图形可视化软件)。它串联交易、库存、支付、履约和对账责任,正常路径展示单写者、命令和发布事实,失败路径展示支付未知、库存失效、退款补偿和四域对账。
复习清单
- 能区分业务能力、Subdomain(子域)、Bounded Context(限界上下文)和 Ubiquitous Language(通用语言)。
- 能用不变量解释 Entity(实体)、
Value Object(值对象)、Aggregate(聚合)和 Aggregate Root(聚合根),而不是背模式清单。 - 能说明数据所有权的语义、写入、正确性和恢复四项责任,以及 Single Writer Principle(单写者原则)不等于单实例。
- 能完整拆分交易、支付、库存、履约四域并说明支付未知、库存失效和履约不可逆事实。
- 能按语义选择 Query(查询)、Command(命令)、Event(事件)及同步、异步集成。
- 能比较 ACL(防腐层)、OHS(开放主机服务)、Published Language(发布语言)、Shared Kernel(共享内核)和 Conformist(遵奉者)。
- 能反驳共享表多写、应用双写和公共大模型,并设计写权迁移。
- 能用 Conway’s Law(康威定律)、Cognitive Load(认知负荷)和价值流评估团队边界。
- 能解释服务边界、部署边界、进程和数据库不要求一一对应。
- 能执行 Schema Evolution(模式演进)的 Expand/Migrate/Contract(扩展/迁移/收缩)和契约废弃。
- 能把跨域一致性拆成局部不变量、流程、查证、补偿、Reconciliation(对账)和人工责任。
- 能从业务键、写路径、契约和发布证据排查 Boundary Drift(边界漂移)、Circular Dependency(循环依赖)和 Distributed Monolith(分布式单体)。
- 能提出拆分、合并、暂缓和重新划界的证据、迁移水位、撤销条件与项目话术。
11. 数量与事实边界自检
| 检查项 | 本册目标 | 自检口径 |
|---|---|---|
知识型 ### | 14 | 每节有知识标记与 3 道六字段题 |
| 六字段题 | 42 | 问题、考点、回答思路、详细答案、进阶追问、进阶回答 |
| 综合题 | 26 | 每题一个 560 至 1000 有效字符口述答案、3 组追问直答、真实相对链接 |
| Mermaid(图表语法) | 15 | 其中 10 张 sequenceDiagram(时序图) |
| Markdown(标记语言)表 | 不少于 16 | 边界表、责任表、演绎表与自检表 |
| 数据演绎 | 14 | 均为 E3(演练证据),输入和公式可复算 |
| PlantUML(统一建模语言) | 1 源加 1 PNG(便携式网络图形) | 原生时序图,不依赖 Graphviz(图形可视化软件) |
事实边界:本册对 WMS(仓储管理系统)、跨境物流、库存、支付和履约的陈述均按 E2(已有材料映射)作为候选项目话术;全部数量算例按 E3(演练证据)使用公开输入和公式;源码、数据库权限、发布记录、真实量级、事故与组织访谈若未读取均保持 E0(待核对)。本文件、PlantUML(统一建模语言)源、真实渲染 PNG(便携式网络图形)和审计结果可作为 E1(源码与可复现证据),但不能外推为生产架构和线上效果。
