面试知识

DDD(领域驱动设计)、限界上下文、聚合、事件风暴与数据所有权

21-分布式系统与微服务 面试知识整理。

DDD(领域驱动设计)、限界上下文、聚合、事件风暴与数据所有权

知识图谱编号:2.2.2。本文消费 2.2.1 单体到微服务演进、拆分原则与成本模型 的“先证明值得拆”结论,产出供 2.2.5 使用的业务边界、聚合不变量和权威数据源。微服务不是起点:先观察业务事实、语言冲突和一致性要求,再决定模块、服务、事务与数据边界。

1. 简历关联与面试主线

WMS(仓储管理系统)的“订单”在销售侧代表客户承诺,在仓内代表履约任务,在支付侧代表交易意图,在账务侧只是一项业务来源。若先按页面或数据库表画“订单服务、库存服务、支付服务”,再套 DDD(领域驱动设计)术语,通常会得到共享表、多服务改同一状态、跨服务联表和分布式事务泛滥。正确主线是:收集命令与已发生事实,找业务规则和冲突语言,归纳 Subdomain(子域),划分 Bounded Context(限界上下文),再用 Aggregate(聚合)承载局部不变量,以 Domain(领域) Event(事件)连接跨边界流程,最后确定唯一写入方和查询方案。

本文闭环旧文档 legacy-k-3.3 的三道章节题、legacy-bank-03 服务拆分、legacy-bank-20 每服务一库、legacy-bank-21 跨服务查询,以及旧表 3 的 DDD(领域驱动设计)概念。旧答案“按业务能力和数据边界拆”被保留,但升级为可验证的推导过程,而不是结论口号。

正式上下文图:

WMS(仓储管理系统)业务事实推导的限界上下文图

  • 节点:订单、库存、履约、支付、账务和查询分别拥有独立模型;ACL(防腐层)隔离支付渠道,读模型承接跨域查询。
  • 箭头:实线表示命令、事件或外部协议方向,虚线表示只读投影;箭头不是数据库外键,也不授权下游反向改写上游数据。
  • 前提:边界来自事件风暴中的业务事实、术语冲突和不变量,不来自现有组织名称或代码包。
  • 失败分支:若订单、履约和账务都能更新“订单状态”或“订单金额”,应先收回写权限并明确各自状态,而不是增加分布式锁。
  • 业务结论:上下文可以与服务一对一,也可以多个上下文先部署在模块化单体中;只有独立演进、隔离故障或扩容收益超过平台与协作成本时才物理拆分。

2.1 从 Domain(领域)与 Subdomain(子域)定义问题空间

Domain(领域)是组织要解决的完整业务问题,不等于系统、部门或数据库。跨境供应链的 Domain(领域)可以是“把货物从客户承诺可靠地转化为仓储、运输和资金结果”。Subdomain(子域)是其中相对独立的业务能力:库存可售、仓内履约、支付交易、会计记账、物流轨迹等。区分核心、支撑和通用 Subdomain(子域),是为了把建模投入投向差异化能力,而不是把所有模块都做成同样复杂的微服务。

flowchart LR
    D["供应链 Domain(领域)"] --> C["核心 Subdomain(子域)<br/>库存承诺与仓内履约"]
    D --> S["支撑 Subdomain(子域)<br/>订单、计费、对账"]
    D --> G["通用 Subdomain(子域)<br/>身份、通知、文件"]
    C --> V{"是否形成差异化规则?"}
    V -->|是| M["深度建模并保护演进节奏"]
    V -->|否| R["复用成熟能力或保持简单模块"]
  • 节点:完整问题空间向核心、支撑、通用 Subdomain(子域)分解。
  • 箭头:表示业务目标到能力分类的推导,不表示运行时调用。
  • 前提:分类依据业务差异化、变化频率和规则复杂度,会随战略调整而变化。
  • 失败分支:把“数据库”“消息队列”当 Subdomain(子域),说明仍在技术解空间,应回到业务目标和决策规则。
  • 业务结论:库存承诺和仓内履约可作为核心能力重点建模,通知与文件通常优先复用,不应为了架构对称而同等拆分。
判断维度核心 Subdomain(子域)支撑 Subdomain(子域)通用 Subdomain(子域)失败边界
竞争差异直接形成履约效率、库存准确率优势支撑核心流程多数企业相似仅按组织大小分类
规则变化高频且依赖业务专家中频、可定制低差异、标准化把当前代码复杂度当业务复杂度
建设策略自研并持续建模自研或采购优先采购、复用所有能力都自研微服务化
WMS(仓储管理系统)例子库存预占、波次与出库订单接入、计费短信、对象存储按表名一表一服务

数据演绎 1:从业务目标而非服务名切分

  • 输入:日均 120000 单、8 个仓、220000 个 SKU(库存单位),库存差错成本每单 180 元;通知失败允许 10 分钟补发。
  • T0:列出决策规则,库存预占需在 80ms 内判断可售,波次需按库位和承运商截单时间优化,通知只负责传达事实。
  • T1:库存与履约被识别为高变化、高损失的核心 Subdomain(子域);通知被识别为通用 Subdomain(子域)。
  • T2:团队把 70% 的建模与压测投入给库存、履约,通知采用成熟平台并保留重试接口。
  • 输出:获得按业务价值排序的建模清单,而不是三个等权“微服务”。
  • 失败分支:若库存规则只是简单增减、规模不足且团队只有 4 人,则保留模块化单体,避免值班和部署成本超过收益。
  • 结论:Subdomain(子域)分类决定投入和边界候选,不直接决定部署单元。

热门面试题

  1. 问题(基础题):Domain(领域)与 Subdomain(子域)有什么区别?

    • 考点:问题空间、能力分解、分类目的。
    • 回答思路:先定义完整业务目标,再说明子问题与战略分类,最后强调不等同于服务。
    • 详细答案:Domain(领域)描述组织试图解决的完整问题,例如跨境供应链要把客户承诺转化为可靠的仓储、运输和资金结果。Subdomain(子域)是问题空间中的能力分区,例如库存承诺、仓内履约、支付交易和账务记账。核心 Subdomain(子域)形成竞争差异,支撑 Subdomain(子域)服务核心流程,通用 Subdomain(子域)通常可复用成熟方案。分类影响建模投入、自研或采购决策,却不能直接推出“一个 Subdomain(子域)一个服务”;规模小、团队少时,多个边界清晰的模块仍可部署在一个单体中。
    • 进阶追问:用户中心是不是天然的核心 Subdomain(子域)?
    • 进阶回答:不天然是。若身份只是登录、角色和资料维护,它通常是通用能力;只有当客户分层、授信或会员权益直接构成竞争规则时,相应部分才可能成为核心 Subdomain(子域)。分类取决于战略,不取决于模块被多少系统调用。
  2. 问题(原理题):为什么不能按现有系统或组织架构直接定义 Domain(领域)?

    • 考点:问题空间与解空间分离、组织惯性。
    • 回答思路:说明系统和部门是历史实现,业务能力会跨越它们,再给出验证方法。
    • 详细答案:现有系统、数据库和部门是某一时期的解空间,可能包含历史包袱。例如订单部门维护一张含支付、履约、退款字段的大表,并不证明这些规则属于同一模型。若直接照搬,边界会固化组织冲突和共享数据。应通过业务目标、命令、事件、规则、不变量、变化原因和责任专家重新识别能力;再用“某规则变化时哪些概念必须一起改”“失败由谁承担”验证内聚性。组织可以影响可实施的部署边界,但不能替代领域证据。
    • 进阶追问:Conway’s Law(康威定律)是否意味着服务必须按团队拆?
    • 进阶回答:它揭示组织沟通结构会映射到系统结构,不是机械设计命令。可以先用清晰模块和接口塑造期望协作,再逐步调整团队;若团队不足以独立开发、值班和承担数据责任,强行一队一服务只会增加交接。
  3. 问题(项目追问题):如何判断 WMS(仓储管理系统)的库存是不是核心 Subdomain(子域)?

    • 考点:业务价值、规则复杂度、量化证据。
    • 回答思路:用差异化、变化频率、失败损失和专家依赖四项判断。
    • 详细答案:我会先看库存是否只是数量增减,还是包含货主、仓库、批次、效期、库位、质检、锁定、可售、预占、波次和渠道配额等规则;再看缺货、超卖和错发的损失,规则是否随大促、仓型和客户合同高频变化,以及是否依赖仓储专家共同决策。若日均十二万单、库存准确率直接影响赔付和履约时效,库存承诺通常是核心 Subdomain(子域)。但核心不等于必须立刻拆服务:应先在模块化单体中建立唯一写入口、聚合不变量和压测基线,再按独立扩容、故障隔离和团队责任决定物理拆分。
    • 进阶追问:订单量下降后要不要把库存服务合回去?
    • 进阶回答:看持续收益而非沉没成本。若独立扩容、发布节奏和隔离价值消失,跨服务一致性与值班成本长期更高,可先收敛接口和数据所有权,再合并部署;逻辑边界仍可保留。

2.2 Bounded Context(限界上下文)与 Ubiquitous Language(通用语言)

Bounded Context(限界上下文)规定一套模型、语言和规则在哪个边界内有效。Ubiquitous Language(通用语言)要求业务专家与开发者在该边界内使用同一语义。边界的价值不是“给服务改名”,而是允许同一词在不同上下文拥有不同模型:订单上下文的“完成”可指客户承诺关闭,履约上下文的“完成”指包裹出库,账务上下文没有可修改的订单状态,只有来源单号和分录状态。

flowchart TD
    T["同一个词:订单完成"] --> O["订单上下文<br/>取消窗口关闭"]
    T --> F["履约上下文<br/>包裹已出库"]
    T --> L["账务上下文<br/>相关分录已过账"]
    O --> X{"是否共享同一状态字段?"}
    F --> X
    L --> X
    X -->|是| E["语义污染与发布耦合"]
    X -->|否| M["各自模型 + 显式事件翻译"]
  • 节点:同一业务词在三个 Bounded Context(限界上下文)中落为不同事实。
  • 箭头:表示语言分歧到模型选择的推导。
  • 前提:每个上下文有明确业务专家、代码模块和变更责任。
  • 失败分支:共享一个万能状态枚举时,新增仓内状态可能迫使账务同步发布。
  • 业务结论:允许语义差异并显式翻译,比追求全企业统一大模型更可维护。
词语订单上下文履约上下文支付或账务上下文失败边界
订单客户承诺与商品行仓内作业来源交易或凭证来源号共享一个巨型订单对象
完成不再接受业务取消包裹已交接承运商分录已过账且平衡一个状态码驱动全部流程
金额应收承诺与优惠后金额申报或计费参考值实收、手续费、税与汇差直接复制并相互覆盖
取消撤销未履约承诺停止或反向仓内任务退款或冲正,不删除分录把取消等同数据库回滚

数据演绎 2:拆开“订单完成”的三种时刻

  • 输入:订单 O-20260714-88,应收 680.00 元,两个包裹,支付渠道手续费 4.08 元。
  • T0 10:00:订单上下文确认客户承诺,版本从 7 变为 8,状态为“已确认”。
  • T1 15:20:履约上下文只完成第一个包裹,履约进度为 1/2;订单仍不可宣称全部履约。
  • T2 次日 09:00:第二个包裹出库,履约发布已全部出库事实;订单关闭取消窗口。
  • T3 次日 23:00:账务上下文把实收 680.00 元拆为现金、手续费和收入分录,借贷各 680.00 元后过账。
  • 输出:三个上下文各自拥有可解释的“完成”,通过事实关联而非共享状态列。
  • 失败分支:若账务在 T1 读取订单的 completed=true 直接确认收入,部分履约和手续费会造成错账。
  • 结论:语言边界必须落实为状态、字段、事件和责任边界。

热门面试题

  1. 问题(基础题):Bounded Context(限界上下文)解决什么问题?

    • 考点:模型适用范围、语言一致、语义隔离。
    • 回答思路:说明同词异义和同义异词,再落到代码、接口、数据与团队责任。
    • 详细答案:Bounded Context(限界上下文)为模型规定有效范围,使边界内的 Ubiquitous Language(通用语言)、规则、状态和数据含义保持一致。它允许“订单”“完成”“金额”在订单、履约、支付和账务中采用不同模型,并通过显式接口或事件翻译,避免一个共享对象承载所有含义。落地时边界应体现在模块、命名、接口契约、数据写权限和责任人上;它可以先存在于模块化单体中,不要求立刻成为网络服务。若两个团队频繁因同一字段含义争论,通常不是再加字段,而是检查上下文是否混杂。
    • 进阶追问:Bounded Context(限界上下文)与微服务必须一对一吗?
    • 进阶回答:不必须。一个上下文可因吞吐或安全原因拆成多个部署单元,多个小上下文也可先共处一个模块化单体。关键是模型和写权限不被跨边界绕过;部署边界要另看扩容、故障、团队和成本。
  2. 问题(原理题):为什么“全公司统一订单模型”经常失败?

    • 考点:语义耦合、变化原因、最小共享内核。
    • 回答思路:用不同业务决策所需字段和生命周期解释模型膨胀。
    • 详细答案:统一模型看似减少转换,实际把不同变化原因压进同一对象。订单关心客户承诺与取消资格,履约关心库位任务和包裹,支付关心交易意图与渠道结果,账务关心借贷分录。它们的状态机、权限、时间尺度和审计要求不同。共享大模型后,一个仓内状态、税务字段或渠道码变更会触发多团队升级,字段可空性不断增加,最终没人能说明哪个值权威。更稳妥的是边界内统一语言,跨边界共享稳定标识与最少事实,并通过 Context(上下文) Map(映射接口)说明翻译关系。
    • 进阶追问:完全不共享模型会不会重复太多?
    • 进阶回答:允许有目的的语义重复。稳定值对象可形成 Shared Kernel(共享内核),但必须有共同治理和同步发布成本;高变化业务对象宁可显式转换,避免隐性耦合。
  3. 问题(项目追问题):WMS(仓储管理系统)里订单与履约应该怎样区分?

    • 考点:状态机、权威事实、业务翻译。
    • 回答思路:分别说明两边决策,再定义事件和失败处理。
    • 详细答案:订单上下文拥有客户承诺、商品行、收货信息快照、取消资格和订单生命周期;履约上下文拥有仓库选择、波次、拣货、复核、包裹和出库状态。订单不能直接修改拣货任务,履约也不能直接改客户订单状态。订单确认后发布事实或发起履约命令,履约接受后建立自己的履约单;出库、缺货、拦截失败再以领域事实回传。跨边界只传必要快照与稳定标识,并为重复、乱序和超时设计幂等与版本检查。这样仓内流程升级不会污染客户承诺模型,订单取消也能得到“尚未拣货、已拣货、已出库”的明确结果。
    • 进阶追问:收货地址由哪边拥有?
    • 进阶回答:客户主数据拥有可编辑地址,订单确认时拥有交易快照,履约消费不可随意变化的配送快照。后续改址必须走显式业务命令并校验履约阶段,不能让履约实时联表读取客户当前地址。

2.3 Event(事件) Storming(事件风暴)从事实推导边界

Event(事件) Storming(事件风暴)不是把现有接口贴到墙上,而是让业务专家、开发、测试和运营按时间线还原业务。先写过去式 Domain(领域) Event(事件),再追问由谁发出什么 Command(命令)、基于什么 Policy(策略)、操作哪个 Aggregate(聚合)、涉及哪个 Actor(角色)与 External System(外部系统)。分歧、等待、批量规则、人工补录和失败补偿属于 Hotspot(热点问题),它们往往比名词更能暴露真正边界。

flowchart LR
    A["Actor(角色)<br/>客户"] --> C1["Command(命令)<br/>提交订单"]
    C1 --> E1["Domain(领域) Event(事件)<br/>订单已确认"]
    E1 --> P1["Policy(策略)<br/>确认后尝试预占"]
    P1 --> C2["Command(命令)<br/>预占库存"]
    C2 --> E2["Domain(领域) Event(事件)<br/>库存已预占"]
    E2 --> P2["Policy(策略)<br/>预占后创建履约"]
    P2 --> C3["Command(命令)<br/>创建履约单"]
    C3 --> E3["Domain(领域) Event(事件)<br/>履约单已创建"]
    C2 --> H["Hotspot(热点问题)<br/>超时结果未知怎么办?"]
  • 节点:角色、命令、事实、策略和热点问题共同还原正常与失败流程。
  • 箭头:表示业务时间和因果方向,不代表必须同步调用。
  • 前提:事件必须是业务人员能判断真假的过去式事实,不能写成“调用库存接口”。
  • 失败分支:若参与者只复述现有系统,会把历史服务边界误当业务边界,应改问“系统停机时业务手工如何处理”。
  • 业务结论:订单确认、库存预占和履约创建分别需要独立规则与失败处理,是三个上下文候选。
flowchart TD
    E["事件时间线"] --> L["语言冲突"]
    E --> I["强不变量"]
    E --> H["Hotspot(热点问题)"]
    L --> B["Bounded Context(限界上下文)候选"]
    I --> A["Aggregate(聚合)候选"]
    H --> X["同步、异步、人工与补偿边界"]
    B --> V{"业务专家能否独立解释?"}
    A --> V
    X --> V
    V -->|能| R["记录边界假设并用场景验证"]
    V -->|不能| Q["继续拆事件或合并候选"]
  • 节点:时间线产生语言、不变量和失败热点三类证据。
  • 箭头:表示从证据到边界假设,再到场景验证的建模闭环。
  • 前提:工作坊结论是待验证假设,不是一次会议后永久冻结的组织图。
  • 失败分支:边界只能用技术名解释、业务专家无法判断责任时,应继续调整。
  • 业务结论:先有事实证据,再有上下文和聚合;服务数量是后续部署决策。
事件风暴元素关键问题WMS(仓储管理系统)示例错误写法产出
Domain(领域) Event(事件)已经发生什么业务事实库存已预占库存接口调用成功时间线与语言
Command(命令)谁希望系统做什么预占订单明细库存调用库存服务意图与权限
Policy(策略)哪个事实触发下一动作预占成功后创建履约消息消费者因果规则
Hotspot(热点问题)哪处有争议、等待或未知结果超时后是否重试预占网络异常风险与补偿候选

数据演绎 3:两小时事件风暴如何收敛出候选边界

  • 输入:6 名参与者、最近 30 个异常订单、74 张事件卡、41 张命令卡、18 个 Hotspot(热点问题)。
  • T0 00:00-00:30:按过去式事实排时间线,合并同义事件后剩 52 个,不讨论系统归属。
  • T1 00:30-01:00:为每个事实补命令、角色和策略,发现“释放库存”既被订单取消触发,也被履约缺货触发。
  • T2 01:00-01:30:标出语言冲突与强规则,归纳订单、库存、履约、支付、账务五个候选上下文。
  • T3 01:30-02:00:用取消、部分出库、支付回调重复三个异常场景验证,合并 3 个过细候选,保留 5 个边界。
  • 输出:上下文假设、未决问题责任人和下一轮原型,不直接产出服务清单。
  • 失败分支:若 18 个热点无人负责,则结论不得进入架构设计,应先补业务决策人。
  • 结论:事件卡数量不是成绩,能否解释规则、失败与所有权才是收敛标准。

数据演绎 4:库存超时热点如何改变交互设计

  • 输入:请求 R-881 预占 SKU(库存单位)A12 件,调用方超时 300ms,库存处理在 420ms 提交。
  • T0:订单发出幂等键 reserve:O-88:1,库存版本为 31,可售 20
  • T1 300ms:订单只知道结果未知,不能把超时解释为失败并直接重新生成新请求键。
  • T2 420ms:库存以原幂等键提交,可售变 8、预占变 12、版本变 32,发布“库存已预占”。
  • T3 700ms:订单查询原幂等键得到成功结果,进入待履约;重复请求重放同一结果。
  • 输出:热点问题推导出幂等键、结果查询和未知态,而非盲目重试。
  • 失败分支:用新请求键重试会再次预占 12 件并造成负库存或虚假缺货。
  • 结论:失败时间线是边界交互契约的一部分。

热门面试题

  1. 问题(基础题):Event(事件) Storming(事件风暴)有哪些核心元素?

    • 考点:事实、意图、策略、角色、外部系统、热点。
    • 回答思路:按时间线说明元素关系,强调不是接口盘点。
    • 详细答案:Event(事件) Storming(事件风暴)通常先用过去式 Domain(领域) Event(事件)还原业务事实,再向前寻找触发它的 Command(命令)和 Actor(角色),向后寻找由事实触发的 Policy(策略)与下一命令,同时标出 Aggregate(聚合)、Read Model(读模型)、External System(外部系统)和 Hotspot(热点问题)。它的价值在于让业务与技术围绕同一时间线暴露语言冲突、不变量、等待、人工决策和失败补偿。产出首先是问题空间认知和边界假设,不是微服务清单、消息主题清单或数据库设计。
    • 进阶追问:线上流程太复杂,一次工作坊画不完怎么办?
    • 进阶回答:先选高价值场景和关键失败路径,例如下单、取消、部分出库与重复回调;建立大图后按热点分小场继续。每轮保留未决问题、责任人和验证期限,不追求一次性完美模型。
  2. 问题(原理题):为什么要先写事件,再写命令和服务?

    • 考点:事实稳定性、避免实现偏见、因果链。
    • 回答思路:说明过去式事实比当前系统调用更接近业务,再讲反向追因。
    • 详细答案:服务和接口是可替换实现,已发生的业务事实相对稳定。先写“库存已预占”“包裹已出库”,参与者能共同判断其含义;若先写“订单服务调用库存服务”,讨论会被现有代码和组织限制。围绕事实向前追命令、权限和不变量,向后追策略、等待与补偿,才能看到同一个事实为何产生、谁能决定、失败后如何收敛。最后再把语言冲突和规则聚类为 Bounded Context(限界上下文),把必须原子维护的规则放入 Aggregate(聚合),物理服务只是部署选项。
    • 进阶追问:技术事件能不能放进事件风暴?
    • 进阶回答:可以作为失败证据或实现备注,但不能替代业务事件。“消息发送失败”有排障价值,“库存预占结果未知”才是业务必须决策的状态;二者应分层表达。
  3. 问题(项目追问题):如何组织一次 WMS(仓储管理系统)事件风暴?

    • 考点:参与者、范围、异常样本、产出验证。
    • 回答思路:给出会前样本、会中步骤、会后验证和失败条件。
    • 详细答案:我会邀请订单、仓储运营、库存、财务、客服、开发和测试代表,以真实订单号、赔付单和异常工单为输入,先限定“确认到出库及取消”场景。会上禁止先讨论系统归属,按时间写事实,再补命令、角色、策略、外部系统和热点;重点展开重复请求、部分成功、超时未知、人工改数和跨日对账。随后按语言冲突、规则内聚和事务要求形成候选边界,用三到五条正常与失败场景验证。会后把未决规则分配给业务责任人,并用代码模块、数据写权限和契约原型验证,而不是会议结束就创建仓库和服务。
    • 进阶追问:业务专家意见冲突时听谁的?
    • 进阶回答:记录冲突背后的场景、权限和损失,交由明确的业务决策人裁定;必要时保留两个上下文模型。技术团队不能用现有字段含义替代业务决策。

2.4 Context(上下文) Map(映射接口)与 ACL(防腐层)

Context(上下文) Map(映射接口)描述 Bounded Context(限界上下文)之间的语义与协作关系,而不是普通调用拓扑。常见关系包括 Customer-Supplier(客户方-供应方)、Conformist(遵奉者)、Shared Kernel(共享内核)、Open Host Service(开放主机服务)、Published Language(发布语言)和 ACL(防腐层)。选择关系时要写清谁主导演进、谁承担翻译、契约如何版本化以及失败时能否降级。

flowchart LR
    O["订单上下文<br/>Customer(客户方)"] -->|需求与兼容协商| I["库存上下文<br/>Supplier(供应方)"]
    I -->|Published Language(发布语言)<br/>库存事实| F["履约上下文"]
    P["支付上下文"] --> A["ACL(防腐层)<br/>验签、码值、状态翻译"]
    A --> X["外部支付渠道<br/>Conformist(遵奉者)风险"]
    P -->|支付已确认事实| L["账务上下文"]
    O -.->|最小 Shared Kernel(共享内核)<br/>订单号值对象| F
  • 节点:上下文与 ACL(防腐层)承担不同模型责任。
  • 箭头:标注协作模式和语义方向,不只是网络方向。
  • 前提:关系有负责人、契约版本和兼容周期。
  • 失败分支:外部渠道模型直接渗入支付聚合,会让渠道码、回调状态污染内部状态机。
  • 业务结论:对内部可协商能力用客户方-供应方关系;对不可控外部系统优先建立 ACL(防腐层)。
映射模式适用关系收益成本失败边界
Customer-Supplier(客户方-供应方)上下游可协商优先级下游需求进入上游计划沟通与兼容成本上游完全不接受下游反馈
Shared Kernel(共享内核)少量稳定概念共同维护减少转换必须同步治理共享整个订单模型
Published Language(发布语言)多消费者稳定集成契约明确、可版本化维护旧版本把内部实体直接发布
ACL(防腐层)外部或遗留模型不可控隔离语义与故障翻译、缓存、对账只做字段复制不做状态映射

数据演绎 5:支付 ACL(防腐层)吸收三种渠道语义

  • 输入:渠道甲返回 SUCCESS,渠道乙返回 PAID,渠道丙先回 ACCEPTED 后需查单;内部交易号 P-9001,金额 680.00 元。
  • T0:ACL(防腐层)验证签名、商户号、金额和回调唯一标识,不让渠道字段进入聚合。
  • T1:甲、乙被翻译为内部“已确认”;丙的 ACCEPTED 被翻译为“处理中”,触发 30s 后查单。
  • T2:查单确认丙成功后,以内部事件标识 pay:P-9001:confirmed:1 发布一次事实。
  • 输出:支付聚合只有待处理、处理中、已确认、失败等内部语义,账务无需理解三个渠道码。
  • 失败分支:若把 ACCEPTED 当成功,账务会提前入账;若只做字符串映射,不校验金额会产生资损。
  • 结论:ACL(防腐层)同时隔离语言、协议、认证和未知结果,不只是转换对象。

热门面试题

  1. 问题(基础题):Context(上下文) Map(映射接口)和系统架构图有什么区别?

    • 考点:语义关系、演进权力、契约责任。
    • 回答思路:对比“谁调用谁”和“谁影响谁的模型”。
    • 详细答案:系统架构图通常展示进程、数据库、消息和网络调用;Context(上下文) Map(映射接口)关注 Bounded Context(限界上下文)之间的模型关系、协作权力和翻译责任。它会说明上游是否接受下游需求、双方是否共享少量内核、是否采用稳定 Published Language(发布语言),以及下游是否需要 ACL(防腐层)。同一条运行时调用可能对应不同关系:内部库存与履约可协商契约,外部支付渠道却不可控。只有把关系写清,团队才能决定兼容周期、版本策略和变更责任。
    • 进阶追问:Context(上下文) Map(映射接口)多久更新一次?
    • 进阶回答:当模型责任、契约主导方、集成方式或团队边界变化时更新,并在架构评审中核对;它不是每日调用拓扑,也不应脱离实际契约长期不维护。
  2. 问题(原理题):ACL(防腐层)为什么不只是对象转换器?

    • 考点:语义翻译、故障隔离、安全与未知结果。
    • 回答思路:从数据结构扩展到状态、协议和恢复策略。
    • 详细答案:对象转换只解决字段形状,ACL(防腐层)还要保护内部模型不受外部语义、协议和失败模式污染。支付场景中,它需要验签、防重放、校验商户与金额,把不同渠道状态映射为内部状态机,处理超时未知、主动查单、限速和原始报文审计。外部新增状态时,变化应尽量停留在 ACL(防腐层),支付聚合只消费稳定的内部结果。若 ACL(防腐层)只是复制字段,渠道码仍会渗入订单、账务和报表,形成跨系统条件分支。
    • 进阶追问:ACL(防腐层)应该独立成服务吗?
    • 进阶回答:不必。它首先是模型边界内的适配模块;只有多系统复用、独立扩容、安全隔离或发布节奏有明确收益时才独立部署,拆分后仍不能成为无业务责任的万能代理。
  3. 问题(项目追问题):订单和库存上下文适合哪种映射关系?

    • 考点:客户方-供应方、契约协商、数据所有权。
    • 回答思路:结合双方规则与组织关系,不机械选模式。
    • 详细答案:订单需要预占、释放和查询预占结果,库存拥有可售规则和写权限。如果同一组织内双方能协商,通常采用 Customer-Supplier(客户方-供应方):订单作为 Customer(客户方)提出取消窗口、批量预占和幂等需求,库存作为 Supplier(供应方)维护通用能力与兼容版本。库存通过 Published Language(发布语言)发布预占、释放、扣减事实;订单保存自己的处理状态,不复制库存可售为权威值。若遗留库存接口不可控,订单侧可加 ACL(防腐层)过渡,但最终写入权仍归库存。
    • 进阶追问:订单能否直接查库存库提高性能?
    • 进阶回答:不能把性能理由变成越权。可由库存提供批量接口、缓存快照或事件投影读模型;直接联库会绕过租户、批次、锁定和版本语义,使数据库结构成为永久公共契约。

2.5 Aggregate(聚合)、Aggregate Root(聚合根)与不变量

Aggregate(聚合)是一组在一次业务命令中需要共同保持一致的对象边界,Aggregate Root(聚合根)是外部修改该边界的唯一入口。聚合不是把有关联的表全部装进一个对象,而是以不变量和并发冲突为依据尽量做小。库存聚合可按“仓库 + 货主 + SKU(库存单位)+ 库存属性”定位,保护 可售 >= 0实物总量 = 可售 + 预占 + 锁定 等局部规则;订单金额、支付结果和账务分录不应塞进库存聚合。

flowchart TD
    C["预占 12 件命令<br/>expectedVersion=31"] --> R["Inventory Aggregate Root(库存聚合根)"]
    R --> V{"版本匹配且 available >= 12?"}
    V -->|是| U["available 20→8<br/>reserved 0→12<br/>version 31→32"]
    V -->|否:版本冲突| RETRY["重读并重新执行业务判断"]
    V -->|否:库存不足| REJECT["返回明确缺货结果"]
    U --> E["记录 InventoryReserved(库存已预占)"]
  • 节点:命令只能通过 Aggregate Root(聚合根)校验版本与库存不变量。
  • 箭头:表示一次原子业务决策和两个拒绝分支。
  • 前提:持久化使用条件更新或等价原子约束,内存校验不能独自防并发。
  • 失败分支:跨聚合持有长事务或把所有仓库库存做成单聚合,会产生热点锁和低吞吐。
  • 业务结论:聚合边界由强一致规则决定,跨聚合流程通过事件、补偿和对账收敛。
设计问题小聚合大聚合选择依据失败边界
一致性只保护局部不变量可在一次事务保护更多对象哪些规则必须即时成立为方便查询扩大聚合
并发冲突范围小热点与锁等待高冲突键和吞吐全仓库存一个聚合
加载数据少、命令聚焦对象图庞大命令所需最小状态每次加载全部历史
跨边界事件与补偿本地直接修改业务容忍窗口假设跨聚合自动强一致

数据演绎 6:库存不变量与乐观版本冲突

  • 输入:聚合键 WH-01/OWNER-7/SKU-A,总量 100,可售 20,预占 70,锁定 10,版本 31;请求甲预占 12,请求乙预占 15
  • T0:甲乙都读到版本 31 和可售 20,内存校验均通过。
  • T1:甲用条件 version=31 and available>=12 更新成功,可售 8、预占 82、版本 32,总量仍为 100
  • T2:乙条件更新影响 0 行,重读版本 32 后发现可售 8,返回库存不足而非继续覆盖。
  • 输出:甲成功、乙失败,任何时刻可售不为负且数量守恒。
  • 失败分支:若先查询再无条件更新,最终可售可能为 -7;若只用分布式锁,锁过期或绕过入口仍可能破坏规则。
  • 结论:聚合方法表达业务规则,数据库条件约束守住并发提交的最后边界。

热门面试题

  1. 问题(基础题):Aggregate(聚合)和 Aggregate Root(聚合根)分别是什么?

    • 考点:一致性边界、唯一修改入口、对象关联区别。
    • 回答思路:用不变量定义聚合,再说明根的访问规则。
    • 详细答案:Aggregate(聚合)是在一次业务命令中必须共同保持不变量的一组实体和值对象,Aggregate Root(聚合根)代表该边界并控制所有状态修改。外部对象只能引用根的稳定标识,不能绕过根直接改内部实体。仓储库存中,可售、预占、锁定和版本可由库存聚合共同维护;预占明细若参与数量守恒,也必须由根创建或释放。聚合边界不等于数据库外键范围,更不等于一个微服务的全部表。应让聚合尽量小,以减少加载和并发冲突,跨聚合规则通过领域服务、事件、流程管理和补偿处理。
    • 进阶追问:一个服务可以有多个 Aggregate(聚合)吗?
    • 进阶回答:当然可以。服务或 Bounded Context(限界上下文)是模型与能力边界,内部通常有多个聚合;一次事务原则上只修改一个聚合,确有强规则时可同库修改少量聚合,但要明确并发和扩展代价。
  2. 问题(原理题):为什么 Aggregate(聚合)要尽量小?

    • 考点:事务范围、冲突概率、加载成本、可扩展性。
    • 回答思路:从一致性收益与并发成本权衡,不绝对化。
    • 详细答案:聚合越大,一次命令能即时保护的规则越多,但锁定或版本冲突范围、加载对象数量和事务时长也越大。把全仓库存、订单、支付和履约放进一个聚合,会让无关 SKU(库存单位)相互竞争,并把网络调用拖进事务。小聚合要求设计跨聚合最终一致,却能让热点按业务键分散、命令更聚焦、失败更可隔离。判断标准是业务是否真的要求同一提交点成立,而不是对象在页面上是否一起展示。若拆小后会短暂出现不可接受且无法补偿的违法状态,再考虑合并或提高事务边界。
    • 进阶追问:每次只改一个聚合是硬规则吗?
    • 进阶回答:是强烈默认而非数学禁令。同一数据库中少量聚合确有原子规则可同事务更新,但必须记录原因;跨服务聚合不能假装一个本地事务,应改用流程、幂等和补偿。
  3. 问题(项目追问题):库存聚合如何防止超卖?

    • 考点:不变量、条件更新、幂等、锁边界。
    • 回答思路:从聚合规则到数据库提交,再到重复与释放。
    • 详细答案:先确定权威聚合键和数量不变量,例如同一仓库、货主、SKU(库存单位)与批次下,可售 >= 0 且实物总量等于可售、预占、锁定等状态之和。预占命令携带业务幂等键,通过 Aggregate Root(聚合根)校验状态,并用版本号或 available >= quantity 条件更新原子提交,同时写预占流水。重复命令返回原结果,支付失败或订单取消通过同一流水释放,出库再把预占转为已扣减。分布式锁可降低热点竞争,但数据库条件和唯一约束才是最后正确性边界,还需用对账发现遗漏释放。
    • 进阶追问:库存按 SKU(库存单位)做聚合会不会太热?
    • 进阶回答:先按仓库、货主、SKU(库存单位)和库存属性细分;极热点可用配额桶或串行化队列,但要保留总账校验与回收机制,不能分片后丢失数量守恒。

2.6 Domain(领域) Event(事件)的事实语义与发布边界

Domain(领域) Event(事件)表达领域中已经发生且值得其他模型关注的事实,名称用过去式,内容应包含事件标识、聚合标识、聚合版本、发生时间、业务必要快照和契约版本。它由聚合状态转换产生,但“本地提交”和“可靠发布”是两个动作;不能在事务提交前把未成事实的事件发给下游,也不能把事件总线当数据库事务的替代品。

sequenceDiagram
    participant C as Command(命令)处理器
    participant A as Aggregate(聚合)
    participant DB as Database(数据库)
    participant O as Outbox(发件箱)
    participant B as Broker(消息代理)
    participant D as 下游上下文
    C->>A: 执行预占命令
    A-->>C: 新状态 + Domain(领域) Event(事件)
    C->>DB: 同一事务保存聚合
    C->>O: 同一事务保存事件
    DB-->>C: 提交成功
    O->>B: 至少一次发布
    B->>D: 可能重复投递
    D->>D: 事件标识 + 聚合版本幂等处理
  • 节点:聚合、业务库、Outbox(发件箱)、消息代理和下游各有独立责任。
  • 箭头:本地状态与待发事件同事务,跨边界投递允许重复。
  • 前提:下游以事件标识去重,并按聚合版本处理乱序或缺口。
  • 失败分支:先发消息后提交数据库会产生幽灵事件;先提交后直接发消息会有永久漏发窗口。
  • 业务结论:领域事件解耦模型,不承诺天然恰好一次;可靠性靠本地原子记录、重试、幂等和对账闭环。
事件设计项推荐做法原因失败表现处理边界
名称业务过去式事实明确不可撤销的发生语义使用“处理库存”像命令分开命令与事实
载荷标识、版本、必要快照降低反查与歧义复制完整内部实体契约最小化并版本化
发布本地事务记录待发事件关闭提交与发送裂缝幽灵或漏发重试与巡检
消费幂等、版本检查、可重放接受重复与乱序重复扣减、旧值覆盖去重表、状态机、对账

数据演绎 7:事件重复与乱序下的履约投影

  • 输入:库存聚合 INV-A 连续产生版本 32 的“已预占”和版本 33 的“已释放”,事件标识分别为 E-32E-33
  • T0:版本 33 因网络路径更快先到履约,履约记录当前版本 33,把待创建任务标为取消。
  • T1:版本 32 后到,消费者发现 32 < 33,不再创建履约任务,只登记迟到指标。
  • T2:事件 E-33 重复到达,去重记录命中,重放原处理结果而不重复取消。
  • 输出:履约最终保持“无有效任务”,与库存最新事实一致。
  • 失败分支:只按到达顺序覆盖会让迟到的版本 32 重新创建任务;只靠消息代理去重无法覆盖消费成功但确认丢失。
  • 结论:事件标识解决重复,聚合版本解决同一实体乱序,业务对账解决长期缺口。

热门面试题

  1. 问题(基础题):什么是 Domain(领域) Event(事件)?

    • 考点:过去式事实、边界解耦、事件载荷。
    • 回答思路:定义事实并说明与命令、集成事件的区别。
    • 详细答案:Domain(领域) Event(事件)是领域模型中已经发生、业务人员关心且可能影响后续决策的事实,例如“库存已预占”而不是“预占库存”。它通常由 Aggregate(聚合)完成状态转换时产生,携带事件标识、聚合标识、聚合版本、发生时间和必要业务快照。事件先服务于边界内模型表达,跨 Bounded Context(限界上下文)发布时可转换为更稳定的 Integration(集成) Event(事件),避免泄露内部实体。事件并不自动提供可靠投递或恰好一次语义,这些由持久化、发布和消费机制负责。
    • 进阶追问:领域事件是否一定要发到 MQ(消息队列)?
    • 进阶回答:不一定。边界内可用于同步通知或记录审计;只有跨进程、削峰或异步解耦有收益时才发布到 MQ(消息队列),且发布前应转换稳定契约。
  2. 问题(原理题):为什么事件要带聚合版本?

    • 考点:乱序、缺口、状态覆盖。
    • 回答思路:区分事件标识去重与版本排序的不同职责。
    • 详细答案:事件标识只能判断同一事件是否重复,不能判断两个不同事件的业务先后。聚合版本随每次成功状态转换单调增加,下游可据此拒绝旧版本覆盖新状态、发现版本缺口并触发补拉。例如版本 33 的释放先到、版本 32 的预占后到,没有版本时履约可能重新创建任务;有版本就能识别迟到。版本只对同一聚合有序,不能用来比较不同聚合的全局先后;跨聚合流程仍需流程状态、因果标识或业务时间窗。
    • 进阶追问:时间戳能替代聚合版本吗?
    • 进阶回答:通常不能。机器时钟可能漂移,同毫秒并发也难排序;数据库提交版本或聚合序号更适合同一聚合顺序,时间戳保留用于观测和业务时效。
  3. 问题(项目追问题):库存预占成功后消息没发出去怎么办?

    • 考点:双写裂缝、Outbox(发件箱)、幂等与对账。
    • 回答思路:先关闭本地原子窗口,再说明至少一次投递和恢复。
    • 详细答案:不能在库存事务提交后临时调用 MQ(消息队列)并寄希望于一次成功。我会在同一数据库事务中保存库存新状态、预占流水和 Outbox(发件箱)事件;后台发布器扫描待发记录,发送成功后更新发布状态,失败按退避重试并报警。消息可能重复,因此履约按事件标识去重、按聚合版本防乱序。还要监控待发年龄、重试次数和版本缺口,定期对比库存预占流水与履约任务,修复长期漏投。这样数据库提交成功但进程崩溃时,事件仍可恢复发布。
    • 进阶追问:更新发布状态失败会不会重复发?
    • 进阶回答:会,所以方案提供至少一次而非天然恰好一次。消费者必须幂等;发布器可用租约并发抢占,但不能把发布端锁当消费正确性的替代。

2.7 用 WMS(仓储管理系统)订单、库存、履约、支付、账务推导边界

边界推导不能从“我们准备建五个服务”开始。先问每个决策由谁作出、依赖哪些权威状态、必须保护什么不变量、失败由谁修复。订单保护客户承诺与取消资格;库存保护数量守恒和不可超卖;履约保护仓内任务顺序与包裹交接;支付保护交易意图、渠道结果与退款资格;账务保护不可变分录和借贷平衡。五套规则的语言、生命周期、审计强度和扩容热点不同,因此形成五个 Bounded Context(限界上下文)候选,但部署时仍可先合并。

flowchart LR
    C1["提交订单"] --> E1["订单已确认"]
    E1 --> C2["预占库存"]
    C2 --> E2["库存已预占"]
    E2 --> C3["创建履约单"]
    C3 --> E3["包裹已出库"]
    E1 --> C4["创建支付意图"]
    C4 --> E4["支付已确认"]
    E4 --> C5["生成账务凭证"]
    C5 --> E5["凭证已过账"]
    E3 --> G{"订单能否关闭?"}
    E5 --> G
    G -->|条件均满足| DONE["关闭客户承诺"]
    G -->|部分完成| WAIT["保持可解释中间态"]
  • 节点:命令与业务事实形成两条可独立失败的履约链和资金链。
  • 箭头:表达因果依赖,支付与履约可以并行,不要求一个跨域大事务。
  • 前提:订单关闭条件由订单上下文判断,只消费其他上下文的事实。
  • 失败分支:支付成功但出库失败时保持中间态,触发退款、改仓或人工决策,不能伪装全局回滚。
  • 业务结论:边界允许局部自治,并用显式流程管理跨域收敛。
flowchart TD
    R["业务规则"] --> O["订单<br/>承诺与取消"]
    R --> I["库存<br/>数量守恒"]
    R --> F["履约<br/>仓内任务"]
    R --> P["支付<br/>渠道交易"]
    R --> L["账务<br/>借贷平衡"]
    O --> D1["订单库唯一写入"]
    I --> D2["库存库唯一写入"]
    F --> D3["履约库唯一写入"]
    P --> D4["支付库唯一写入"]
    L --> D5["账务库唯一写入"]
    D1 --> Q["跨域读模型"]
    D2 --> Q
    D3 --> Q
    D4 --> Q
    D5 --> Q
  • 节点:业务规则先形成模型边界,再推导唯一写入和只读投影。
  • 箭头:从规则到数据所有权是因果方向,不能反过来按现有库划业务。
  • 前提:每项数据有一个权威写入方,下游只保存自身决策所需快照。
  • 失败分支:把共享库当集成总线会使五个上下文重新耦合在表结构上。
  • 业务结论:数据物理隔离是所有权的强化手段,不是边界发现手段。
上下文核心决策聚合与不变量权威数据不应拥有
订单是否接受、取消、关闭承诺订单聚合;金额与状态转换合法订单行、承诺快照、取消资格实时可售、渠道账务状态
库存是否可预占、释放、扣减库存聚合;可售非负、数量守恒库存余额、预占流水、版本客户订单总状态
履约如何分仓、拣货、复核、出库履约单;任务顺序与包裹状态仓内任务、包裹、交接事实支付渠道状态
支付与账务交易是否确认;分录是否平衡支付交易;账务凭证渠道交易、退款;不可变分录修改订单或库存余额

数据演绎 8:一单两包裹与支付成功的跨边界状态

  • 输入:订单 O-700,商品甲 2 件、乙 1 件,应收 680.00 元,支付幂等键 pay:O-700:1
  • T0:订单版本 5→6 确认;库存分别预占甲 2、乙 1,生成两个独立预占流水。
  • T1:支付渠道确认 680.00 元,支付版本 2→3;账务尚未过账,订单展示“已支付、待履约”。
  • T2:包裹一出库,履约进度 1/2;包裹二因质检锁定,库存乙仍为预占。
  • T3:账务按实收 680.00 元生成平衡分录;订单仍不能标记全部履约。
  • T4:乙改仓后出库,履约发布 2/2,订单关闭承诺。
  • 输出:各上下文状态均可解释,聚合内即时一致,跨上下文最终收敛。
  • 失败分支:订单用一个 status=SUCCESS 同时代表支付、出库和入账,会在 T1-T4 之间丢失真实中间态。
  • 结论:跨域流程要组合事实,不要复制一个全局状态机。

数据演绎 9:取消命令如何跨边界判定而非级联改库

  • 输入:订单 O-701 已预占甲 5 件,履约任务 F-9 已拣 3 件、未拣 2 件,支付未发起。
  • T0:订单收到取消请求,状态进入“取消处理中”,请求标识 cancel:O-701:1
  • T1:履约判断已拣部分不可自动撤销,返回“需人工回库 3 件、可取消 2 件”。
  • T2:库存只释放可取消 2 件,预占从 5→3;其余等待回库事实。
  • T3:操作员复核回库 3 件,履约关闭任务,库存释放剩余预占。
  • T4:订单收齐履约关闭和库存释放事实,完成取消。
  • 输出:每个上下文执行自身规则,订单编排业务进度但不跨库更新。
  • 失败分支:订单直接把库存预占改为 0 会掩盖已拣实物,造成账实不符。
  • 结论:跨边界取消是新业务流程,不是数据库级联回滚。

热门面试题

  1. 问题(基础题):服务拆分应该按什么标准?

    • 考点:迁移 legacy-k-3.3-q1legacy-bank-03,业务能力、数据、事务、组织边界。
    • 回答思路:先讲边界发现,再讲部署决策和不拆条件。
    • 详细答案:先通过事件风暴识别命令、事实、语言冲突、业务规则和失败热点,形成 Subdomain(子域)与 Bounded Context(限界上下文);再以聚合不变量确定事务边界,以唯一权威写入方确定数据所有权。最后才评估是否独立部署:变化频率、扩容热点、故障隔离、安全合规、团队自治和平台成熟度是否足以覆盖网络、发布、值班与最终一致成本。WMS(仓储管理系统)中订单、库存、履约、支付、账务有不同规则,可先成为模块;库存并发和履约发布节奏达到阈值后再拆。不能按控制器、页面、表或团队人数机械切分。
    • 进阶追问:为什么不能一张表一个服务?
    • 进阶回答:表是持久化结构,不是业务能力。一个不变量可能跨多表,一张表也可能包含多个语义;按表拆会产生大量同步调用和跨服务事务,使对象关联变成网络耦合。
  2. 问题(原理题):为何服务边界、事务边界和数据边界不能视为同一个?

    • 考点:五类边界分离、部署与一致性差异。
    • 回答思路:分别回答独立演进、原子规则和唯一写入。
    • 详细答案:服务边界回答哪个能力需要独立部署、扩容和隔离;事务边界回答哪些不变量必须在一个原子提交点成立;数据边界回答谁是权威写入方。它们相关但不等价。一个库存服务可有多个库存聚合和事务;多个 Bounded Context(限界上下文)也可暂时共享部署单元,但仍保持模块与写权限。若拆服务后仍让订单直接改库存表,服务边界并未带来数据自治;若为了一个页面把五个上下文放进跨服务事务,则部署拆分与业务一致性冲突,应调整流程或重新合并。
    • 进阶追问:什么时候应把两个服务合回去?
    • 进阶回答:当它们长期同步变更、同一团队值班、独立扩容与故障隔离无收益,而跨服务事务和调用成本持续高时,应保留逻辑模块后合并部署与数据事务边界。
  3. 问题(项目追问题):库存与订单到底应不应该拆成两个服务?

    • 考点:迁移 legacy-k-3.3-q3,条件化决策、回滚与合并。
    • 回答思路:给出小规模与复杂规模两种答案,再说明拆后契约。
    • 详细答案:没有脱离规模和规则的固定答案。小团队、低并发、库存仅服务订单且发布节奏一致时,可在模块化单体中保留订单与库存两个清晰模块,用本地事务降低复杂度。当库存被多个渠道共享,包含批次、效期、质检、库位和配额规则,且并发热点、故障隔离和独立扩容收益明显时,再拆独立服务。拆后库存是可售与预占的唯一写入方;订单用幂等命令申请预占,处理成功、失败和未知态,以事件推进流程,并用释放流水、补偿任务和对账闭环。若收益消失,接口和所有权清晰反而便于合并部署。
    • 进阶追问:下单失败后库存冻结如何释放?
    • 进阶回答:按预占流水和业务幂等键发起释放;重复释放返回原结果,超时先查状态,长期失败进入重试、告警和人工核对,不能只依赖消息自动成功。

2.8 数据所有权、每服务一库与共享库边界

Data Ownership(数据所有权)指一个上下文对某类业务事实拥有唯一权威写权限、规则解释权、模式演进权和修复责任。Database per Service(每服务一库)是强化所有权的部署模式,不是 DDD(领域驱动设计)的定义。物理独库、同实例不同 Schema(模式)、同库不同表都可实现所有权,前提是其他服务不能绕过所有者直接写;Shared Database(共享数据库)在迁移期可接受,但必须限制写权限、登记退出条件,Shared Table(共享表)则通常是高风险耦合。

flowchart TD
    D["一类业务数据"] --> O{"是否有唯一规则解释与写入方?"}
    O -->|否| C["先明确 Data Ownership(数据所有权)"]
    O -->|是| P{"隔离、扩容、合规是否需要物理独库?"}
    P -->|是| DB["Database per Service(每服务一库)"]
    P -->|暂不需要| S["同实例不同 Schema(模式)或表"]
    DB --> X["接口、事件、读模型供其他上下文使用"]
    S --> X
    C --> F["禁止用共享表掩盖责任不清"]
  • 节点:先确定所有权,再选择物理隔离级别。
  • 箭头:表示治理顺序,而不是从独库自动得到边界。
  • 前提:数据库账号、代码依赖、变更审批和审计都能执行唯一写入。
  • 失败分支:多服务持有同表写权限时,任何服务都无法独立保证不变量或演进模式。
  • 业务结论:同库不同 Schema(模式)可作为低成本阶段,达到隔离收益阈值后再迁独库。
数据模式自治与隔离查询便利运维成本适用与退出条件
Database per Service(每服务一库)最强,权限和故障可隔离跨域查询复杂备份、连接、治理成本高高价值边界;收益下降可合并实例
同库不同 Schema(模式)逻辑隔离,可独立授权管理查询较方便中等迁移期或中小规模;禁止跨模式写
Shared Database(共享数据库)不同表依赖权限与规范联查方便较低模块化单体或过渡;需模式负责人
Shared Table(共享表)最差,多写方冲突表面方便隐性发布成本最高仅遗留止血;必须建立收口计划

数据演绎 10:共享库存表收回写权限

  • 输入:订单、履约、库存三个应用都能写 inventory_balance,日写入 900000 次;近月 17 次差错中 11 次无法定位写入方。
  • T0:为所有写入增加应用身份审计,统计两周,发现订单写释放占 8%、履约写扣减占 21%、库存应用占 71%
  • T1:库存上下文提供预占、释放、扣减幂等命令;订单和履约切换为影子调用,比对结果但暂不关闭旧写。
  • T2:差异率从 0.7% 降到 0.02%,修复剩余批次和质检语义后,撤销订单写权限。
  • T3:再撤销履约写权限,库存成为唯一写入方;跨域报表改读投影。
  • 输出:同一物理库中先实现逻辑所有权,后续可按负载迁独库。
  • 失败分支:一次性断权且无影子校验,会把隐藏写路径变成生产故障;永久双写则形成两个权威源。
  • 结论:所有权迁移要有审计、影子、差异阈值、断权和回滚窗口。

热门面试题

  1. 问题(基础题):为什么提倡 Database per Service(每服务一库)?

    • 考点:迁移 legacy-bank-20,自治、模式演进、故障隔离。
    • 回答思路:先讲收益,再明确不是绝对物理规则。
    • 详细答案:Database per Service(每服务一库)能用数据库权限强化 Data Ownership(数据所有权),让服务独立演进模式、选择存储、扩容和备份,并减少其他服务绕过业务规则直接写表的机会。代价是跨服务 Join(连接)消失、连接与运维数量增加、事务和报表更复杂。因此核心原则是唯一写入和契约访问,不是每个小服务都必须购买独立实例。同实例不同 Schema(模式)也可作为阶段方案,只要账号隔离、禁止跨边界写并有变更责任人;共享表多写则必须治理。
    • 进阶追问:每服务一库是不是每个服务一个数据库实例?
    • 进阶回答:不是。它强调逻辑所有权;可在同实例中用独立数据库或 Schema(模式)隔离。是否物理独立取决于性能、合规、容灾和成本。
  2. 问题(原理题):共享数据库为什么会破坏服务自治?

    • 考点:隐式契约、写权限、发布耦合、故障范围。
    • 回答思路:区分“共享实例”和“共享模型、多写方”。
    • 详细答案:真正危险的不是共用硬件,而是多个服务把表结构当公共接口并直接写同一业务数据。表字段、索引、锁和事务就成为未版本化契约,一个服务改列或状态码会迫使其他服务同步发布;任何写方都可能绕过聚合规则,事故后也难确定责任。共享连接池和大查询还会扩大资源故障范围。治理应先确定唯一所有者,收回写权限,其他上下文通过 API(应用程序接口)、事件或读模型使用;再根据隔离收益决定是否迁移物理实例。
    • 进阶追问:只读账号直接查别人的库可以吗?
    • 进阶回答:临时排障或受控报表可接受,但不能成为在线核心契约。表结构变化会击穿消费者,且读取可能绕过租户和语义;应逐步替换为版本化接口或读模型。
  3. 问题(项目追问题):遗留系统共享表如何平滑迁移到唯一所有者?

    • 考点:审计、影子流量、双写边界、断权与回滚。
    • 回答思路:给出发现、封装、比对、切换、删除五步。
    • 详细答案:先盘点代码、数据库账号、定时任务和人工脚本,对写入增加应用身份与业务请求标识审计,确定真实写路径。然后由目标所有者封装幂等命令和状态查询,旧写方进入影子调用,比较数量、版本和结果。差异清零并压测后,按调用方分批把旧写切为新命令,保留短期可回切但禁止长期双主;最终撤销数据库写权限、报警任何越权写入,并把跨域查询迁到读模型。整个过程要定义差异阈值、回滚窗口和人工修复清单。
    • 进阶追问:迁移期双写如何保证一致?
    • 进阶回答:双写只作短期影子校验,指定一个权威源,另一路结果不可反向驱动业务;用请求标识关联差异并可重放。不要把永久双写包装成一致性方案。

2.9 跨服务查询、API(应用程序接口) Composition(接口聚合)与 Read Model(读模型)

跨上下文页面不能成为共享数据库的理由。简单、低调用量且需要较新数据时,可用 API(应用程序接口) Composition(接口聚合)并行调用所有者;高频列表、搜索、报表和多条件排序应由领域事件构建 Read Model(读模型)。读模型的数据是派生副本,有延迟、可重建、不可反向写入;聚合接口也要有超时预算、部分失败语义和数据新鲜度说明。

flowchart LR
    U["运营查询"] --> G{"查询形态"}
    G -->|少量对象、近实时| A["API(应用程序接口) Composition(接口聚合)"]
    A --> O["订单 API(应用程序接口)"]
    A --> I["库存 API(应用程序接口)"]
    A --> F["履约 API(应用程序接口)"]
    G -->|高频列表、搜索、报表| R["Read Model(读模型)"]
    O -.->|事件投影| R
    I -.->|事件投影| R
    F -.->|事件投影| R
    R --> S["显示数据时间与延迟"]
  • 节点:查询形态决定接口聚合或读模型,不决定业务写入位置。
  • 箭头:实线为查询,虚线为异步投影。
  • 前提:调用方知道新鲜度、缺失字段和部分失败语义。
  • 失败分支:聚合十几个服务会放大尾延迟;读模型被业务命令反写会形成第二权威源。
  • 业务结论:在线决策读所有者,运营检索读投影;二者可并存。
方案一致性与延迟复杂度适用场景失败边界
API(应用程序接口) Composition(接口聚合)接近各源当前值,存在跨调用时间差调用治理复杂单详情、少量依赖扇出过多、尾延迟、部分失败
Read Model(读模型)最终一致,可标数据时间建设投影与重放列表、搜索、报表延迟、乱序、不可反向写
批量导出可用离线快照调度和存储成本财务、运营大报表在线库被大查询拖垮
直接跨库 Join(连接)看似实时模式与资源耦合仅受控临时分析成为在线公共契约

数据演绎 11:订单工作台从接口扇出迁到读模型

  • 输入:列表每页 50 单,需订单、库存、履约、支付、账务五域字段;峰值 400 QPS(每秒查询率),单域 P95(95 分位响应时间)分别 35/60/80/120/90ms(毫秒)
  • T0:逐行调用形成最多 250 次下游请求,连接池耗尽,页面 P95(95 分位响应时间)达到 2.8s(秒)
  • T1:改为五个批量 API(应用程序接口)并行调用,页面降至 310ms(毫秒),但支付抖动时整页仍部分缺失。
  • T2:以领域事件构建订单工作台 Read Model(读模型),正常延迟 P99(99 分位响应时间)小于 8s(秒),查询降至 75ms(毫秒)
  • T3:页面展示“数据更新于 14:03:12”,支付缺口可点单据号回源查询。
  • 输出:高频列表走投影,单据决策仍回权威 API(应用程序接口)。
  • 失败分支:用读模型余额直接执行退款,可能基于延迟值产生资损。
  • 结论:查询性能优化不能改变写所有权和关键决策数据源。

热门面试题

  1. 问题(基础题):微服务如何做跨服务查询?

    • 考点:迁移 legacy-bank-21,接口聚合、读模型、离线快照。
    • 回答思路:按数据新鲜度、查询复杂度、吞吐和失败容忍选型。
    • 详细答案:少量实体详情、依赖不多且需要接近实时值时,可由聚合层并行调用各数据所有者的批量 API(应用程序接口),设置总超时预算并定义部分失败展示。高频列表、搜索、多维报表和复杂排序应消费领域事件建立 Read Model(读模型),接受明确延迟,提供数据时间、重放和校验能力。大规模财务或运营导出可用离线快照。不要让在线业务长期跨库 Join(连接),因为它把表结构、资源和权限重新耦合。关键写决策仍需回到权威上下文。
    • 进阶追问:读模型延迟多大可以接受?
    • 进阶回答:由业务决策决定。运营看板可接受秒级或分钟级,退款余额通常不可用延迟投影;应定义 SLO(服务等级目标)、展示数据时间并提供回源通道。
  2. 问题(原理题):API(应用程序接口) Composition(接口聚合)为什么容易出现尾延迟?

    • 考点:扇出、最大值效应、部分失败、超时预算。
    • 回答思路:说明页面耗时由最慢依赖主导,并给治理方式。
    • 详细答案:聚合请求要等待多个下游,整体延迟接近最慢调用加编排开销;依赖越多,至少一个慢请求或失败的概率越高。逐行调用还会把一页查询放大为数百次网络请求,耗尽线程与连接池。治理包括提供批量接口、限制扇出、并行调用、为总链路分配连接和读取超时、缓存稳定字段、定义部分失败与降级,并对非实时列表改用 Read Model(读模型)。不能用无限重试掩盖慢依赖,否则查询会反向压垮所有者。
    • 进阶追问:GraphQL(图查询语言)能自动解决吗?
    • 进阶回答:它能统一查询契约和字段选择,但不会消除后端扇出、数据所有权或尾延迟;解析层仍要批量、缓存、限流并选择读模型。
  3. 问题(项目追问题):WMS(仓储管理系统)运营工作台怎么设计?

    • 考点:读模型字段、事件投影、回源、可重建。
    • 回答思路:区分浏览与决策,说明延迟、乱序、校验和故障。
    • 详细答案:工作台的列表、筛选和搜索走专用 Read Model(读模型),按订单号聚合订单承诺、预占摘要、履约进度、支付摘要和账务状态。各上下文以事件标识和聚合版本投影,消费者幂等处理、拒绝旧版本、发现版本缺口后回源补齐;页面展示数据时间和投影延迟。操作员点击退款、强制释放等高风险动作时,后端必须回权威上下文重新校验状态与权限。读模型可从事件或权威快照重建,不能被工作台直接更新业务字段。
    • 进阶追问:事件丢失导致工作台缺字段怎么办?
    • 进阶回答:监控版本缺口和投影延迟,提供按聚合补拉、全量快照重建与源投影对账;缺字段时明确展示未知,不伪造默认成功状态。

2.10 CQRS(命令查询职责分离)的适用边界

CQRS(命令查询职责分离)把改变业务状态的 Command Model(命令模型)与面向展示的 Query Model(查询模型)分开。它首先是模型职责分离,不等于必须使用两个数据库、Event(事件) Sourcing(事件溯源)或 MQ(消息队列)。订单写模型只暴露确认、取消等意图并保护不变量;工作台查询模型按检索需求冗余字段。只有读写复杂度、吞吐、模型变化原因明显不同,且团队能承担投影延迟、重建和排障时才值得采用。

flowchart LR
    U["用户或系统"] --> C["Command(命令)<br/>确认、取消、预占"]
    C --> W["Write Model(写模型)<br/>聚合与不变量"]
    W --> DB["权威写库"]
    DB --> E["Domain(领域) Event(事件)"]
    E --> P["Projection(投影器)<br/>幂等、版本检查"]
    P --> R["Query Model(查询模型)<br/>列表、搜索、报表"]
    U --> Q["Query(查询)"]
    Q --> R
    R -.->|高风险动作回源校验| W
  • 节点:写模型承担规则,查询模型承担展示,投影器连接两者。
  • 箭头:写侧到读侧允许延迟;查询不能反向篡改权威写库。
  • 前提:业务能说明延迟窗口,投影可重放、可对账。
  • 失败分支:简单增删改查也套双模型,会增加事件、存储与值班成本而无收益。
  • 业务结论:先做逻辑职责分离,只有负载或隔离需求明确时再物理分库。
CQRS(命令查询职责分离)形态写读存储一致性复杂度适用边界
同模型同库相同本地即时简单业务、低吞吐
分模型同库相同库,不同对象或视图可即时或短延迟中低查询字段与写规则不同
分模型分库独立写库和读库最终一致高频搜索、独立扩容、隔离
Event(事件) Sourcing(事件溯源)写侧事件为权威历史依赖重放与投影很高强审计且事件语义成熟,不是默认方案

数据演绎 12:CQRS(命令查询职责分离)延迟窗口与关键动作回源

  • 输入:订单 O-99014:00:00.000 取消,写模型版本 18→19;工作台投影 SLO(服务等级目标)为 5s(秒)
  • T0:取消事务提交并记录版本 19 事件,权威状态立即为“已取消”。
  • T1 14:00:01:操作员列表仍显示版本 18 的“待出库”,页面同时显示数据时间。
  • T2:操作员点击“强制出库”,命令端回源读取版本 19,因订单已取消拒绝操作。
  • T3 14:00:03:投影消费版本 19,列表更新为“已取消”,延迟 3s(秒) 未违反目标。
  • 输出:读侧短暂陈旧不导致错误写入,因为命令端重新校验不变量。
  • 失败分支:若按钮依据读模型直接更新履约库,会在延迟窗口继续出库。
  • 结论:CQRS(命令查询职责分离)必须把“展示可旧”和“决策必须新”明确分开。

热门面试题

  1. 问题(基础题):CQRS(命令查询职责分离)是什么?

    • 考点:读写职责、模型分离、非强制分库。
    • 回答思路:先定义命令和查询,再讲渐进形态。
    • 详细答案:CQRS(命令查询职责分离)把改变状态的命令模型与读取展示的查询模型分离。写模型围绕业务意图、权限、不变量和聚合设计,不为任意报表暴露对象图;查询模型可以按页面冗余、排序和搜索需求组织。分离可从同库不同对象开始,并不天然要求两个数据库、MQ(消息队列)或 Event(事件) Sourcing(事件溯源)。当读写负载、模型和扩容需求差异明显时,才通过事件投影到独立读库,并承担延迟、重复、乱序、重建和对账成本。
    • 进阶追问:CQRS(命令查询职责分离)与读写分离一样吗?
    • 进阶回答:不一样。数据库读写分离主要复制同一数据模型以分担负载;CQRS(命令查询职责分离)允许读写模型按不同业务职责设计,物理部署可相同也可不同。
  2. 问题(原理题):为什么 CQRS(命令查询职责分离)不能解决所有复杂查询?

    • 考点:投影成本、业务语义、数据新鲜度。
    • 回答思路:说明它转移复杂度而非消灭复杂度。
    • 详细答案:CQRS(命令查询职责分离)把在线 Join(连接)和扇出成本转移到投影构建,但必须处理事件契约、延迟、版本缺口、重放、模式变更和源投影对账。若业务要求每次查询都跨五个上下文读取同一瞬间强一致快照,普通异步投影无法满足;应重新审视是否真有该不变量,或把相关数据合并到同一边界。对于低频查询,批量 API(应用程序接口)或离线快照可能更便宜。方案选择要比较总拥有成本,不能把架构复杂度隐藏在消费者里。
    • 进阶追问:读模型可以直接修数据吗?
    • 进阶回答:只能修复派生投影,例如重放或补拉;业务事实必须在权威写模型按命令和审计修正,随后重新投影,禁止把读模型改成第二写源。
  3. 问题(项目追问题):WMS(仓储管理系统)哪些场景适合 CQRS(命令查询职责分离)?

    • 考点:工作台、库存搜索、报表与命令回源。
    • 回答思路:列适合与不适合场景,说明延迟目标。
    • 详细答案:订单履约工作台、跨仓库存搜索、轨迹列表和运营报表适合查询模型,因为它们读取多域字段、筛选维度多、流量高且通常能容忍秒级延迟。库存预占、释放、强制出库、退款和账务冲正不应依据投影直接执行,命令端必须回权威聚合校验版本、状态和权限。实施时先定义投影字段、事件版本、延迟 SLO(服务等级目标)、缺口补拉和全量重建,再按监控证明是否需要独立读库。简单仓库或低频后台页面可先用批量接口,避免过早引入双模型运维。
    • 进阶追问:库存可售查询能不能读投影?
    • 进阶回答:展示和搜索可读带时间戳的投影;下单预占必须向库存权威模型提交命令并重新判断,投影值不能作为扣减前提。

2.11 遗留系统边界迁移、Strangler Fig Pattern(绞杀者模式)与 ACL(防腐层)

遗留大表通常同时承载订单、库存、履约和财务语义,不能靠一次数据库拆分获得正确边界。应先识别所有权,围绕旧系统建立 ACL(防腐层)和新命令入口,以 Strangler Fig Pattern(绞杀者模式)按业务能力迁移:先读旁路与影子比对,再切唯一写入口,再迁历史数据和读流量,最后删除旧写。每一步都需要权威源、幂等键、差异阈值、回滚窗口与人工修复方案。

flowchart LR
    C["调用方"] --> R{"路由阶段"}
    R -->|旧能力| L["Legacy(遗留)单体"]
    R -->|新库存命令| A["ACL(防腐层)"]
    A --> N["新库存上下文"]
    L -.->|变更捕获,仅校验| V["差异比对"]
    N -.->|权威结果| V
    V --> G{"差异率低于阈值?"}
    G -->|是| CUT["撤销旧库存写权限"]
    G -->|否| FIX["修正规则与数据后重放"]
  • 节点:路由、遗留单体、ACL(防腐层)、新上下文和比对形成可回退迁移链。
  • 箭头:实线是业务流量,虚线是校验流量;校验副本不成为权威源。
  • 前提:迁移期间明确只有一个写权威,任何双写都有截止日期。
  • 失败分支:同时让新旧系统接受写入且互相覆盖,会形成无法收敛的双主。
  • 业务结论:先迁能力与规则,再迁物理数据;差异证据决定切换,不靠主观“测试通过”。
迁移阶段权威写源校验方式回滚方式退出条件
盘点与封装遗留系统写路径审计、规则样本无流量变化写方与不变量清楚
影子与回放遗留系统同请求影子执行、结果比对停止影子差异可解释且低于阈值
分批切写新上下文双读比对、事件对账租户或仓维度回切新写稳定、旧写已拦截
收尾新上下文历史校验、越权写报警数据快照与修复脚本旧列、账号、代码删除

数据演绎 13:按仓切换库存所有权

  • 输入:8 个仓、220000 个 SKU(库存单位)、旧库存表 1.6 亿行;目标差异率小于 0.01%,切换批次按仓进行。
  • T0:保留旧系统权威写,回放 7 天命令到新模型,初始差异 1.8%,定位批次和质检锁定语义缺失。
  • T1:修正规则后差异降至 0.006%,剩余差异均有人工工单;先切低流量仓 WH-08
  • T2WH-08 新系统成为唯一写源,旧写拦截并报警,连续 48h(小时) 无未解释差异。
  • T3:逐仓切换,任一仓差异超过 0.01% 即停止后续批次,不把已稳定仓整体回退。
  • T4:全部仓稳定 14 天后撤销旧账号、归档旧列和补偿脚本。
  • 输出:库存所有权按可隔离业务分片迁移,回滚范围受控。
  • 失败分支:全量一次切换会把规则遗漏、数据量和回滚压力叠加;永久变更捕获双向同步会形成双主。
  • 结论:迁移成功标准是权威唯一、差异闭环和旧路径删除。

热门面试题

  1. 问题(基础题):如何从共享库单体迁移到数据自治?

    • 考点:先逻辑后物理、唯一权威、渐进切换。
    • 回答思路:按盘点、封装、影子、切写、迁读、删除回答。
    • 详细答案:先通过代码、账号和审计盘点真实写路径,基于业务规则确定目标 Bounded Context(限界上下文)及唯一所有者。由所有者封装幂等命令,其他模块停止新增直接写;随后用 ACL(防腐层)适配遗留模型,回放历史请求或影子执行并比较状态、数量和版本。差异达到阈值后按仓、租户或业务类型分批切换唯一写源,再迁查询到 API(应用程序接口)或 Read Model(读模型),最后撤销旧账号、删除旧写代码和无主字段。物理迁库可晚于写权限收口,避免同时改变过多变量。
    • 进阶追问:为什么不先拆数据库?
    • 进阶回答:若规则和所有权未清,拆库只把本地共享表变成跨网共享接口,新增同步、双写和事务问题;先明确业务边界才能知道数据该去哪里。
  2. 问题(原理题):迁移期为什么必须指定唯一权威源?

    • 考点:双主冲突、可回放、差异解释。
    • 回答思路:说明影子双写与业务双主的区别。
    • 详细答案:新旧系统同时可接受业务写入时,网络延迟、重试和规则差异会产生不同顺序与结果,任何一方都无法确定覆盖方向。唯一权威源使另一侧只作影子计算或派生同步,差异可以按请求标识解释并重放;切换时权威角色在明确时刻转移,旧写立即拦截。短期双写若不可避免,也必须由一个事务或可靠事件驱动,并声明权威和截止日期。没有这些约束,所谓平滑迁移会演化为永久双主。
    • 进阶追问:变更数据捕获能保证双主一致吗?
    • 进阶回答:不能。它可传播已提交变更,却不了解两边业务规则和冲突意图;适合单向投影或迁移,不应替代所有权设计。
  3. 问题(项目追问题):WMS(仓储管理系统)库存迁移如何控制资损和超卖风险?

    • 考点:分片切换、条件约束、差异阈值、止损。
    • 回答思路:按仓分批,保留数据库底线与可回切路由。
    • 详细答案:我会先冻结旧表新增语义,对历史预占、释放、扣减和质检锁定做全量对账;新库存模型保留可售非负、数量守恒、请求唯一键和版本条件更新。影子期同请求在新模型计算但不影响业务,用仓库、货主和 SKU(库存单位)维度比对。切换按低流量仓开始,路由配置保证一个仓同一时刻只有一个写源;监控差异率、负库存、重复预占和待释放年龄。超过阈值立即停止批次并回切该仓,已稳定仓不必全部回退。最终撤销旧写权限并持续对账。
    • 进阶追问:回切时新系统已经产生的写怎么办?
    • 进阶回答:先暂停该仓命令,按事件序号把新权威期间的变更单向回放到旧系统并核对,再切路由;不能直接开双写抢跑。

2.12 边界漂移审查、线上排查与回迁条件

边界不是画完即永久正确。代码依赖、数据库账号、调用链、发布记录和事故会暴露 Boundary Drift(边界漂移):跨上下文直接写表、共享内部实体、一个页面同步扇出过多服务、两个团队总是同步发布、聚合锁冲突持续升高。治理目标不是维持服务数量,而是恢复清晰责任;修复可以是收回写权、建立读模型、调整聚合、增加 ACL(防腐层),也可以把收益不足的服务合回模块化单体。

flowchart TD
    S["现象:差错、超时、同步发布"] --> E["证据:审计写入、调用、版本、事件缺口"]
    E --> K{"哪类边界冲突?"}
    K -->|数据所有权| O["收回写权限、指定权威源"]
    K -->|事务与聚合| A["调整不变量与聚合边界"]
    K -->|查询| R["批量接口或 Read Model(读模型)"]
    K -->|服务成本| M["合并部署或回迁模块化单体"]
    O --> V["回归、对账、权限与指标验证"]
    A --> V
    R --> V
    M --> V
  • 节点:从现象、证据到边界类型和修复方案。
  • 箭头:要求先取证再改边界,避免看到超时就盲目加缓存。
  • 前提:保存请求标识、聚合版本、写入身份、事件版本和发布记录。
  • 失败分支:只修某次数据而不收回越权写路径,差错会再次出现。
  • 业务结论:合并服务是合法治理手段,逻辑边界可以在同一进程内继续保持。
证据暗示问题优先动作验证指标可能回迁条件
多应用写同表数据所有权缺失审计并收回写权限越权写为 0合并到唯一所有者模块
跨服务事务高频失败聚合或事务边界冲突重审强不变量补偿率、未知态下降同团队且原子规则不可拆
列表扇出十余服务查询模型错误批量接口或读模型P95(95 分位响应时间)、依赖数低流量时简化为模块查询
长期同步发布和值班服务收益不足合并部署评估发布独立度、事故恢复时长独立扩容和隔离价值消失

数据演绎 14:用证据判断两个服务是否合并

  • 输入:订单与履约服务连续 90 天发布 24 次,其中 21 次同步;调用 P95(95 分位响应时间)85ms(毫秒),跨服务补偿每周 37 次,均由同一 6 人团队值班。
  • T0:统计独立扩容,两个服务峰值 CPU(中央处理器)均低于 35%,没有安全或合规隔离要求。
  • T1:抽样补偿发现 29/37 次来自订单取消与履约状态必须共同判断,接口往返增加未知态。
  • T2:在模块化单体原型中保留两个 Bounded Context(限界上下文)模块和独立表,取消流程改本地编排,压测 P95(95 分位响应时间)降至 24ms(毫秒)
  • T3:灰度合并部署后四周补偿降至每周 3 次,发布仍能按模块测试,写权限未混合。
  • 输出:物理服务合并,逻辑模型、数据所有权和事件接口仍清晰。
  • 失败分支:若履约需要按仓独立扩容或由不同值班团队承担,合并可能扩大故障域,应保留服务并修正事务设计。
  • 结论:以独立演进和隔离收益减去协调成本决定部署,不以“拆过就不能合”决定。

热门面试题

  1. 问题(基础题):如何发现微服务边界划错了?

    • 考点:运行证据、边界漂移、非静态设计。
    • 回答思路:从数据、事务、调用、发布和组织五类证据回答。
    • 详细答案:典型信号包括多个服务直接写同一表、服务间传递内部实体、跨服务事务和补偿长期高频、一个页面同步扇出过多服务、聚合热点锁持续升高、两个服务几乎每次同步发布且同一团队值班。排查时要关联数据库写入身份、请求标识、聚合版本、事件缺口、调用延迟、发布记录和事故责任,判断冲突属于服务、事务、数据、查询还是组织边界。修复可能是收回写权、调整聚合、增加 ACL(防腐层)或读模型,也可能合并服务;不应只加重试和缓存掩盖模型问题。
    • 进阶追问:代码循环依赖是否一定说明边界错?
    • 进阶回答:是强信号但要结合业务。若双方规则确实共同变化,可能应合并;若只是查询方便,应改契约或投影。不能仅通过提取公共模块把语义循环藏起来。
  2. 问题(原理题):为什么微服务可以合并而 Bounded Context(限界上下文)仍保留?

    • 考点:逻辑边界与部署边界分离、回迁策略。
    • 回答思路:说明进程合并不等于模型混合。
    • 详细答案:Bounded Context(限界上下文)规定语言、模型和责任范围;微服务是部署与运行边界。两个上下文可在同一进程中保持独立模块、接口、表和写权限,由本地调用替代网络调用,减少延迟、补偿和值班成本。只要不共享内部实体、不跨模块改表,未来仍可按需求再拆。相反,两个独立进程若共享表和模型,也不具备真正边界。回迁应保留契约测试、模块依赖规则和数据所有者,避免“合并”演变成无结构单体。
    • 进阶追问:合并后还要用 Domain(领域) Event(事件)吗?
    • 进阶回答:业务事实仍有建模价值,可在进程内同步分发或记录 Outbox(发件箱);是否走 MQ(消息队列)取决于异步、可靠和隔离需求。
  3. 问题(故障题):线上出现库存与订单不一致,如何排查边界问题?

    • 考点:证据链、止血、权威源、修复验证。
    • 回答思路:按影响、止血、取证、根因、修复、回归回答。
    • 详细答案:先确认影响仓、租户、订单和数量,暂停高风险释放或人工改数但保留查询;用订单号、预占幂等键和 TraceId(链路标识)串联订单状态、库存余额、预占流水、Outbox(发件箱)、消费记录和数据库写入身份。库存余额与流水是库存权威,订单只表示流程认知。若发现订单直接写库存表,立即收回越权权限;若事件漏发,按流水补发;若消费乱序,按聚合版本重建。修复必须经审批,记录前后值,再做全量源投影对账、越权写报警和异常场景回归,避免只改一行数据。
    • 进阶追问:如何快速止血又不破坏证据?
    • 进阶回答:先限流或关闭特定写入口,冻结自动补偿,保留日志、消息位点、数据库审计和快照;所有人工修复使用独立工单与幂等脚本,不直接无痕改表。

3. 线上排查 SOP(标准操作流程)与项目话术

边界事故排查顺序: 先确定影响与权威源,再止血和保存证据;按请求标识、业务幂等键、聚合版本、事件标识、数据库写入身份串联命令、事务、发布和消费;区分事实错误、流程认知滞后和查询投影延迟;最后才执行补发、重放、冲正或人工修复。验证至少包括越权写为零、聚合不变量成立、事件无缺口、读模型可重建和相同失败场景回归。

可直接复述的 WMS(仓储管理系统)项目话术: 我们没有先按表把单体切成服务,而是从真实异常单做 Event(事件) Storming(事件风暴),识别订单承诺、库存数量守恒、仓内履约、支付交易和账务借贷五套不同规则。先在模块化单体中建立 Bounded Context(限界上下文)、Aggregate Root(聚合根)和唯一写入口;库存用条件更新、幂等流水和版本守住不可超卖,跨边界用 Domain(领域) Event(事件)推进。运营列表走 Read Model(读模型),退款与强制出库回权威模型校验。只有库存并发、履约发布和故障隔离收益达到阈值后才独立部署,并保留按仓回切、对账与人工兜底。这样既获得自治,也避免为“微服务数量”支付不必要成本。

3.1 综合题库过渡(非知识型)

本节只用于结束上一知识小节,不配置 kb:knowledge 标记,也不重复六字段题。下面 30 道题用于训练 560 至 900 个有效字符的完整口述,并通过相对链接回到 12 个权威知识小节。

4. 高频综合面试题与追问

  1. 问题(综合题):如何从业务问题推导微服务边界,而不是先画服务?

    • 口述答案:我的结论是先形成可验证的业务边界,再决定是否独立部署,微服务名称不能作为建模输入。第一步从真实订单、异常工单和人工处理记录还原业务时间线,用过去式事实描述“订单已确认、库存已预占、包裹已出库、支付已确认、凭证已过账”,再补充触发事实的命令、角色、策略、外部系统和失败热点。第二步观察同词异义、规则变化原因和业务专家责任,把订单承诺、库存守恒、仓内履约、支付交易、账务借贷聚成不同 Bounded Context(限界上下文)。第三步用必须在一次提交中成立的不变量确定 Aggregate(聚合),以唯一权威写入方确定数据所有权。到这里得到的是逻辑模块,不是五个服务。最后才评估独立扩容、故障隔离、发布节奏、安全合规和团队值班收益,是否超过网络延迟、最终一致、契约兼容和平台成本。WMS(仓储管理系统)中我会先在模块化单体落实写权限和事件契约,库存热点或履约独立发布达到量化阈值后再拆;若两个能力长期同步发布且由同一团队承担,就保留逻辑边界但合并部署。验证不是看服务数量,而是检查业务专家能否解释责任、越权写是否为零、聚合不变量是否可证明、跨边界失败能否补偿和对账。最后还要留下不拆、回切和合并条件,避免架构只有前进方向。详情见领域、子域与边界推导。 评审记录应保存当时订单量、团队人数、调用跳数和事故基线,三个月后用同一口径复盘;若独立交付率未提高而补偿与值班人时持续增加,就停止继续拆分。
    • 追问 1:按团队拆服务可不可以?
    • 直接回答:团队结构是实施约束,不是业务证据;先确保模型内聚,再让团队承担端到端责任,团队不足以独立值班时不应强拆。
    • 追问 2:什么时候直接保持单体?
    • 直接回答:低并发、小团队、发布节奏一致、故障隔离收益低时,模块化单体更合适,但模块和数据写权限仍需清晰。
    • 追问 3:边界一次能定准吗?
    • 直接回答:不能。先记录假设,用异常场景、发布记录和运行指标验证,允许拆分、调整聚合或合并部署。
  2. 问题(综合题):Domain(领域)、Subdomain(子域)与 Bounded Context(限界上下文)是什么关系?

    • 口述答案:Domain(领域)是组织要解决的完整问题空间,例如跨境供应链要把客户承诺可靠转化为仓储、运输和资金结果。Subdomain(子域)是问题空间中的能力分区,可按竞争差异、规则复杂度和建设策略分为核心、支撑与通用能力;库存承诺和仓内履约可能是核心,订单接入和计费可能是支撑,通知与文件通常偏通用。Bounded Context(限界上下文)则是某套模型和 Ubiquitous Language(通用语言)生效的边界,它属于问题空间向解空间落地的连接点。同一个 Subdomain(子域)可能因模型差异形成多个 Bounded Context(限界上下文),一个小型 Bounded Context(限界上下文)也不必独立部署。实际推导时,我先用 Subdomain(子域)回答“企业在解决哪些子问题、哪里值得深度投入”,再用 Bounded Context(限界上下文)回答“某个词、规则和状态在哪个范围内成立”。比如订单上下文的完成是客户承诺关闭,履约上下文的完成是包裹出库,账务上下文则是分录过账;强行统一会产生万能状态和同步发布。落地验证要看语言是否无歧义、业务专家能否独立裁决规则、代码和数据写权限是否匹配、跨边界是否有明确翻译。分类还会随业务战略变化,必须定期复核。它们都不直接等于服务,部署仍要经过扩容、隔离、组织能力和总成本判断。详情见领域与限界上下文
    • 追问 1:核心 Subdomain(子域)一定要自研吗?
    • 直接回答:通常值得掌握核心规则,但可组合采购能力;关键差异模型和决策权不能完全外包,非差异部分可复用。
    • 追问 2:一个 Subdomain(子域)能有多个限界上下文吗?
    • 直接回答:可以,例如支付子域可分交易、退款、对账等上下文,取决于语言、规则和生命周期是否真正独立。
    • 追问 3:限界上下文如何体现在代码里?
    • 直接回答:用独立模块、命名、接口、依赖规则、数据权限和责任人落实;仅在图上画边框没有作用。
  3. 问题(综合题)legacy-bank-03 服务拆分应该遵循什么完整方法?

    • 口述答案:服务拆分不是“高内聚、低耦合”八个字,而是一套证据链。先看是否值得拆:当前单体是否因发布冲突、局部扩容、故障扩散或团队协作产生可量化瓶颈;若没有,先做模块化治理。确需演进时,从 Event(事件) Storming(事件风暴)收集命令、事实、策略、角色、外部系统和热点问题,通过语言冲突与变化原因识别 Bounded Context(限界上下文)。然后列出每个上下文的业务决策,使用 Aggregate(聚合)保护本地强不变量,指定 Data Ownership(数据所有权)与唯一写入口。跨边界只传稳定标识、必要快照和版本化事件,不共享内部实体。接着分别建模服务边界、事务边界、数据边界、可观测性边界和组织成本,不能用一张服务图替代全部。WMS(仓储管理系统)中,订单、库存、履约、支付、账务可以先作为模块;库存因多渠道共享和并发热点独立,履约因仓内流程与发布节奏独立,账务因审计要求独立。拆分实施采用逐能力迁移、唯一权威、影子比对和按仓回切。验收看独立发布率、故障爆炸半径、调用延迟、补偿率、越权写、团队值班负担;收益不足时允许合并。旧写路径未删除、数据仍多写或回滚不可执行,都不能算拆分完成。详情见事件风暴与边界推导。 每次切分只选择一个可按仓或租户隔离的能力,预先保存数据快照、路由开关和增量回放位点;灰度期间同步比较业务成功率与守恒差异,任一超阈值立即回切。
    • 追问 1:为什么不能按数据库表拆?
    • 直接回答:表是存储结构,一个不变量可能跨多表,一张表也可能混合多种语义;按表拆会制造细碎调用和跨服务事务。
    • 追问 2:拆分顺序怎么定?
    • 直接回答:优先边界清楚、收益高、依赖少且可回滚的能力,例如报表读模型或独立库存写入口,不先动最混乱的核心链路。
    • 追问 3:拆分完成的标志是什么?
    • 直接回答:旧写路径被删除、权威唯一、契约可演进、失败可闭环且运行收益达到预设阈值,不是新服务上线即完成。
  4. 问题(综合题):如何用 Event(事件) Storming(事件风暴)发现 WMS(仓储管理系统)的真实边界?

    • 口述答案:我会把 Event(事件) Storming(事件风暴)当成跨角色的业务取证,而不是画现有系统。会前选择下单、取消、部分出库、缺货换仓、支付重复回调等高价值场景,准备真实订单号、赔付单和人工工单,邀请仓储运营、订单、库存、财务、客服、开发与测试。会上第一轮只按时间写业务人员可判断真假的过去式事实,例如“库存已预占”,禁止写“库存接口调用成功”。第二轮向前补谁发出了什么 Command(命令),向后补哪个 Policy(策略)触发下一动作,同时标出 Actor(角色)、External System(外部系统)和 Hotspot(热点问题)。第三轮围绕语言冲突、强不变量、等待与未知结果聚类:订单关心承诺和取消,库存关心数量守恒,履约关心任务与实物,支付关心渠道结果,账务关心借贷平衡。最后用异常路径验证边界,例如预占超时但实际成功时,库存必须拥有幂等结果查询,订单只能进入未知态而不能直接改库存。会后产出边界假设、未决规则责任人、命令与事件草案,再用模块依赖、数据权限和原型验证。若业务专家不能解释某边界、热点无人裁决或结论只能用系统名表达,就不能直接创建服务。详情见事件风暴方法。 工作坊结束还要把每个热点变成可验证问题,例如谁拥有改址资格、预占超时多久转人工、部分出库能否退款;指定责任人和截止日期,再用真实异常单回放验证。 收敛标准不是卡片数量,而是五个上下文的命令权限、权威数据和异常责任都有答案;未决项进入决策日志,在原型和流程演练中验证,未关闭前不扩大服务拆分范围。
    • 追问 1:事件卡越多越好吗?
    • 直接回答:不是。应以覆盖关键规则和失败路径为准,同义事件要合并,无法影响决策的技术噪声要分层记录。 为避免只覆盖正常链路,我还会要求现场展开库存不足、仓库断网、支付已成但出库失败三条时间线;每个中间态都要有人解释客户看到什么、谁能重试和何时转人工。
    • 追问 2:业务专家冲突怎么办?
    • 直接回答:记录各自场景、权限和损失,由明确业务决策人裁定;也可能说明两个上下文各自语义都成立。
    • 追问 3:工作坊后马上拆服务吗?
    • 直接回答:不。先把结论当假设,在模块、契约、数据写权和异常场景中验证,再决定部署。
  5. 问题(综合题):Bounded Context(限界上下文)如何避免“统一大模型”失控?

    • 口述答案:统一大模型失控的根因是把不同变化原因、生命周期和权限压进同一对象。WMS(仓储管理系统)的“订单”在客户侧是承诺和取消资格,在履约侧是仓内任务来源,在支付侧是交易关联,在账务侧只是凭证来源号。若共享一个订单实体和状态枚举,新增仓内状态会迫使财务升级,支付渠道码会渗入运营判断,可空字段不断增加,最终没人知道哪个金额或状态权威。Bounded Context(限界上下文)的做法是让边界内使用一致 Ubiquitous Language(通用语言),并允许边界间同词异义。订单拥有应收承诺和地址快照,履约拥有包裹与库位任务,支付拥有实收与渠道状态,账务拥有不可变分录。跨边界只共享稳定标识和必要事实,通过 Context(上下文) Map(映射接口)声明上游下游关系、版本策略和翻译责任。少量真正稳定的值对象可采用 Shared Kernel(共享内核),但需共同治理;外部或遗留模型用 ACL(防腐层)隔离。验证时检查内部实体是否被跨边界引用、状态码是否需要多团队同步发布、同一字段是否存在多个写方。目标不是消灭语义重复,而是让每份数据的业务意义、快照时点、决策权和失败责任可解释。详情见限界上下文与上下文映射。 接口评审还要拒绝“公共订单对象”这类隐性共享内核:消费者只能拿到完成自身决策所需字段,字段来源和契约版本必须可追踪;新增语义应协商契约而非直接读表。 最终还要用一次跨域变更验证隔离性:新增履约包裹状态时,订单与账务无需改内部模型;若它们必须同步改枚举,说明统一大模型仍以事件契约形式泄漏。
    • 追问 1:重复保存订单号和金额是不是坏味道?
    • 直接回答:不一定。稳定标识和业务快照可以重复,关键是声明来源、时间和是否权威,禁止双方互相覆盖。 数据目录可以记录订单号等跨域标识和来源,却必须标明每个金额是应收、实收还是入账,不允许把同名字段自动判定为同一语义。
    • 追问 2:企业级统一数据字典还有价值吗?
    • 直接回答:有,用于标识、单位和交换契约;它不能强迫不同上下文共享同一生命周期模型。
    • 追问 3:如何防止边界内语言继续漂移?
    • 直接回答:让业务专家参与命名与评审,在代码、事件契约和测试中使用同一术语,并把歧义作为建模问题处理。
  6. 问题(综合题):Aggregate(聚合)如何划分,为什么不能按对象关系划?

    • 口述答案:Aggregate(聚合)由必须在一次业务命令和原子提交中保持的不变量划分,而不是由页面展示、数据库外键或对象包含关系决定。先列业务规则,再问如果短暂不一致会造成什么损失、能否补偿。库存场景中,同一仓库、货主、SKU(库存单位)和库存属性下,可售不得为负,实物总量要等于可售、预占、锁定等状态之和,因此这些状态和对应预占流水由库存 Aggregate Root(聚合根)统一修改。订单金额、支付结果和履约任务虽然业务相关,却不参与该局部数量守恒,不应塞进库存聚合。聚合要尽量小,因为越大,加载数据、锁范围、版本冲突和事务时长越高;全仓库存一个聚合会让无关商品互相阻塞。实现上,聚合方法先校验命令和状态,数据库再用版本号、条件更新与唯一索引守住并发提交底线。跨聚合流程默认通过 Domain(领域) Event(事件)、流程状态、补偿和对账收敛,不能因想要“方便事务”把网络调用放进聚合事务。若某条跨聚合规则确实不可短暂违反且无法补偿,应重新评估边界或合并事务范围。验证包括并发冲突率、不变量查询、重复命令重放、释放对账和热点键分布。详情见聚合与不变量。 还要用并发预占、预占后取消、已扣减后误释放三类反例证明状态机:条件更新影响零行必须回读并重新决策,不能简单覆盖;守恒失败时冻结聚合并转人工修复。 聚合设计文档要明确命令、允许状态、拒绝原因和数据库约束,并由并发测试证明;仅画对象关系而没有可执行不变量,不能作为事务边界依据。
    • 追问 1:一个事务能改两个聚合吗?
    • 直接回答:默认避免;同库且确有强规则可例外,但要记录原因和冲突成本,跨服务不能假装本地事务。 聚合边界上线后要观察版本冲突和事务时长:冲突过高可能聚合键太粗,跨聚合补偿过多则可能边界太细,两类信号都应触发重新建模。
    • 追问 2:聚合根等于数据库主表吗?
    • 直接回答:不等于。聚合根是业务修改入口,持久化可映射多表、文档或其他结构,主表只是实现。
    • 追问 3:分布式锁能保护聚合吗?
    • 直接回答:可降低竞争,但锁过期、旁路写和故障都存在,数据库条件与唯一约束仍是正确性底线。
  7. 问题(综合题):库存聚合如何同时处理超卖、重复、释放和并发?

    • 口述答案:我会把库存正确性建立在权威聚合、幂等流水和数据库原子条件上,而不是只靠缓存锁。先定义聚合键,例如仓库、货主、SKU(库存单位)、批次和质量属性,并规定可售非负、数量守恒。预占命令携带稳定业务键 reserve:订单号:版本 和期望数量,Aggregate Root(聚合根)校验当前状态后,以 available >= quantity 和版本匹配的条件更新提交,同时写入唯一预占流水;并发请求只有一个能命中旧版本,失败方必须重读后重新做业务判断。重复请求命中唯一键后返回原结果,不能再次扣减。支付失败、订单取消或履约缺货都通过原预占流水申请释放,释放也有状态机和幂等键;已经转扣减的流水不能被普通释放逆转,需要退货或回库流程。Redis(远程字典服务)锁或队列可用于热点削峰,却不能替代数据库约束。跨边界消息采用 Outbox(发件箱)记录,消费者按事件标识和聚合版本处理重复乱序。线上监控负库存、数量守恒差异、待释放年龄、条件更新冲突率和事件积压,并定期用库存余额对预占、扣减、回库流水。异常修复走审计命令,不能直接改余额。详情见库存聚合数据演绎。 大促前要以峰值并发压测冲突率,热点 SKU(库存单位)超过阈值时可引入配额桶或串行化削峰,但每个分桶都有回收期限和总账核对,吞吐优化不能改变权威源。 还应区分业务拒绝和并发冲突:库存不足直接返回稳定结果,版本冲突才允许在预算内重读;两者都不能无限重试,否则热点时会把正确保护机制放大为流量风暴。
    • 追问 1:缓存中的库存能直接扣吗?
    • 直接回答:可做流量闸门或配额,但数据库权威状态和流水必须最终确认;缓存成功不能代表业务成功。 对账不能只比较余额总数,还要按业务键确认每笔预占都有释放或扣减终态;超龄流水按仓和货主告警,修复前先冻结同一聚合的高风险人工操作。
    • 追问 2:预占超时怎么处理?
    • 直接回答:进入结果未知态,用原幂等键查询或重试,不能生成新键再次预占。
    • 追问 3:释放失败怎么办?
    • 直接回答:保留待释放流水,退避重试、超龄告警、对账和人工工单;不能仅修改订单为已取消。
  8. 问题(综合题):Domain(领域) Event(事件)如何设计才不会变成“消息泥球”?

    • 口述答案:Domain(领域) Event(事件)首先是模型中的过去式业务事实,其次才可能成为跨进程消息。名称应表达已发生结果,如“库存已预占”,而不是“处理库存”或“调用成功”。事件至少携带全局事件标识、聚合标识、聚合版本、发生时间、契约版本和下游作决策所需的最小快照;不要直接序列化内部实体,否则字段和生命周期会成为公共契约。边界内事件若要跨 Bounded Context(限界上下文),应转换为稳定 Integration(集成) Event(事件),通过 Published Language(发布语言)说明语义与兼容策略。可靠性上,聚合状态和 Outbox(发件箱)在一个本地事务提交,发布器至少一次发送;消费者用事件标识去重、聚合版本防同一实体乱序,版本缺口触发补拉或重放。事件不提供天然恰好一次,也不能把尚未提交的意图伪装成事实。模式变更采用新增可选字段、双版本消费和淘汰窗口,重大语义变化发布新事件。监控待发年龄、重复率、迟到率、版本缺口和死信,并以权威流水与投影对账。这样事件解耦模型,而不会把全部字段和所有业务意图塞进一个巨大消息。详情见领域事件发布边界。 发布前还应做契约测试和敏感字段审查,消费者登记所用字段与最大延迟;删除字段前统计真实消费并经过兼容窗口。事故恢复先补权威快照,再按版本追平。 事件治理还需要目录、所有者和废弃日期;无人消费且无审计价值的事件应停止发布,高价值事件则保留契约样例和重放工具,避免消息主题持续增长却无人负责。
    • 追问 1:事件里能否只放标识让下游回查?
    • 直接回答:可以但会增加耦合和回查压力;应按稳定性、新鲜度和隐私权衡,提供下游决策所需最小快照。 对跨上下文事件还要区分领域发生时间和发布接收时间,消费延迟不改变事实先后;发现版本缺口时先隔离该聚合,补齐后再恢复,不能全局停机。
    • 追问 2:事件顺序怎么保证?
    • 直接回答:同一聚合按版本和分区键维持局部顺序,下游仍需拒绝旧版本;不要承诺所有聚合全局有序。
    • 追问 3:消息成功是否代表业务完成?
    • 直接回答:不代表。它只说明投递或确认,业务完成要看下游状态、补偿和对账证据。
  9. 问题(综合题):Context(上下文) Map(映射接口)如何指导上下游团队协作?

    • 口述答案:Context(上下文) Map(映射接口)的价值是把模型影响力、契约责任和翻译成本显式化,而不只是画调用箭头。先列出上下文及其数据所有者,再判断关系:订单依赖库存预占能力,双方在同一组织可采用 Customer-Supplier(客户方-供应方),订单提出批量、取消和幂等需求,库存维护通用规则与兼容版本;库存向履约发布稳定 Published Language(发布语言),避免暴露内部表。少量稳定的订单号值对象可形成 Shared Kernel(共享内核),但要有共同负责人和同步变更规则。外部支付渠道不可控,支付上下文建立 ACL(防腐层),负责验签、防重放、渠道状态翻译、主动查单、限速和原始报文审计,不能让渠道码渗入账务。每条映射关系都应记录谁主导版本、下游能否影响上游计划、兼容窗口、故障降级和退出条件。若上游完全不协商,下游可能暂时 Conformist(遵奉者),但应评估模型污染风险。评审时将映射图与真实接口、消息、代码依赖和团队责任对照,发现“图上隔离、代码共享”就视为失败。详情见上下文映射与防腐层。 每条关系还要写升级通知期、旧版本下线条件和回滚联系人;上游长期无法满足关键需求时,下游可用快照或 ACL(防腐层)止损,但不能复制写逻辑形成第二权威源。 Context(上下文) Map(映射接口)评审必须由上下游共同参加,不能由架构人员单方面宣布关系;争议要落到版本优先级、故障责任和成本承担,才具有协作约束力。
    • 追问 1:Shared Kernel(共享内核)能放哪些内容?
    • 直接回答:只放少量稳定、双方共同治理的值对象或协议类型,不放高变化实体、仓储规则和全局工具包。 关系选择不是永久标签。上游开放稳定契约后,下游可逐步删除临时 ACL(防腐层);反之共享内核频繁联动时,应缩小共享范围并改为发布语言,降低同步发布。
    • 追问 2:内部系统也需要 ACL(防腐层)吗?
    • 直接回答:需要时可以,尤其遗留模型、不可控版本或语义差异明显;是否内部不决定污染风险。
    • 追问 3:上下文映射与组织图不一致怎么办?
    • 直接回答:先暴露实际协作成本,可调整团队责任或暂时用契约治理;不能假装组织限制不存在。
  10. 问题(综合题):支付上下文为什么必须通过 ACL(防腐层)连接外部渠道?

    • 口述答案:外部支付渠道的状态、协议、重试和失败语义由第三方控制,直接进入内部模型会污染交易和账务。不同渠道可能用 SUCCESSPAIDACCEPTED 表示不同阶段,ACCEPTED 甚至只代表受理,若订单或账务直接判断字符串就可能提前发货或入账。ACL(防腐层)先验证签名、商户号、金额、币种、时间窗和回调唯一标识,保存原始报文用于审计,再把渠道状态翻译为内部“待处理、处理中、已确认、失败、未知”等稳定状态。超时或受理态不猜结果,而是按限速策略主动查单;重复回调用渠道通知标识和内部交易号幂等,状态转换受 Aggregate(聚合)保护。对外请求和内部事务之间存在裂缝,因此用 Outbox(发件箱)、状态机、补偿和对账收敛,不能宣称一个数据库事务回滚外部支付。ACL(防腐层)还负责隔离渠道版本升级、错误码、认证和熔断,让账务只消费“支付已确认”及金额、手续费等内部事实。验证包括签名失败率、未知态年龄、回调查单一致率、重复处理为零和渠道账单对账,异常必须有人工核准入口。详情见支付 ACL(防腐层)演绎。 渠道升级前先回放脱敏报文做契约验证,再按商户或币种灰度;新渠道返回无法映射的状态时保持内部未知并报警,绝不能默认成功,原始报文和翻译版本均需满足审计保留期。 资金场景还要设置双重验证:实时状态由回调与查单收敛,日终状态由渠道账单对账;两者冲突时冻结自动出款并进入差错工单,不能让 ACL(防腐层)自行猜测。
    • 追问 1:ACL(防腐层)是否等于网关?
    • 直接回答:不等于。网关做通用路由和安全横切,ACL(防腐层)理解支付语义、状态翻译和恢复规则,属于支付边界。 ACL(防腐层)的缓存只保存查询结果和渠道限速信息,不能缓存决定资金状态的最终答案;渠道报文与内部事件要用同一关联号串联,便于资损追溯。
    • 追问 2:支付成功后订单能直接改为完成吗?
    • 直接回答:不能。支付只发布交易事实,订单结合履约和取消规则推进自己的状态,账务也独立过账。
    • 追问 3:渠道回调和主动查单冲突怎么办?
    • 直接回答:以渠道交易号、内部版本和合法状态机幂等归并,必要时以渠道最终查询与对账文件核准并保留审计。
  11. 问题(综合题):Data Ownership(数据所有权)到底包含哪些责任?

    • 口述答案:Data Ownership(数据所有权)不是“这张表归哪个服务”的资产登记,而是对一类业务事实承担唯一写入、规则解释、模式演进、访问授权、质量监控、修复审批和事故值班的完整责任。库存上下文拥有可售、预占、锁定、扣减和释放的定义,订单与履约不能绕过库存 Aggregate Root(聚合根)改余额;它们只能发幂等命令、消费事实或保存用于自身决策的快照。所有者要发布版本化 API(应用程序接口)或事件,说明字段语义、新鲜度、兼容窗口和失败结果,也要提供对账与人工修复入口。数据库层通过独立账号、最小权限、审计日志和模式负责人强化责任,但物理独库不是前提。同库不同 Schema(模式)同样可以建立所有权,反之多个服务即使有不同库,若互相双写同步,也没有唯一权威。发生差错时先找权威流水和版本,不由最后展示页面的数据决定真假。治理指标包括越权写次数、未知数据负责人数量、契约破坏次数、修复时长和源投影差异。若无人能批准修复或多个团队都能覆盖同一状态,就说明所有权仍未建立。数据所有者还要公开容量、保留期和合规边界,不能只拥有写权限却把查询与质量责任推给下游。详情见数据所有权与物理模式。 模式变更必须由所有者提供兼容路径和回滚方案,消费者不得依赖未发布列;事故修复后还要复盘为什么约束、审计或对账没有提前发现,而不是只追究值班人员。 对跨团队访问还应提供字段分级、租户过滤与审计,所有者能回答谁在何时读取或修改了什么;缺少访问证据时,即使写入唯一,也无法承担合规和事故责任。
    • 追问 1:数据所有者是否可以拒绝所有查询需求?
    • 直接回答:不能。它应提供稳定接口、事件或投影能力,但有权拒绝绕过规则的直接写和不受控在线大查询。 所有权评审还要检查备份恢复:谁决定恢复点、恢复后如何与事件位点衔接、下游如何重新投影。没有恢复责任的数据边界在事故中仍会变成公共问题。
    • 追问 2:主数据和业务快照谁权威?
    • 直接回答:主数据拥有当前可编辑值,订单等业务上下文拥有发生当时的交易快照,两者语义不同,不能相互覆盖。
    • 追问 3:数据修复由谁做?
    • 直接回答:所有者提供经审批、可审计、幂等的修复命令;其他团队提交证据和工单,不直接改库。
  12. 问题(综合题)legacy-bank-20 每服务一库是不是微服务的硬性要求?

    • 口述答案:我的回答是否定的。Database per Service(每服务一库)是强化 Data Ownership(数据所有权)和故障隔离的常见模式,不是微服务成立的硬性定义,更不等于每个服务必须购买独立数据库实例。核心要求是一类业务数据有唯一权威写入方,其他上下文通过契约访问,不能把表结构当公共接口。物理上可分为独立实例、同实例独立数据库、同库不同 Schema(模式)甚至模块化单体中的不同表;选择取决于性能隔离、合规、备份恢复、扩容、团队能力和成本。独立实例能减少资源争抢和越权写,但带来连接、监控、备份、容灾、跨域查询和事务复杂度。对于六人团队、低吞吐系统,一服务一实例可能使运维成本远高于收益;可先在共享实例上以账号和 Schema(模式)隔离,等库存热点、账务合规或容灾目标出现再物理迁移。反过来,共享表由多服务直接写,即使部署了多个实例也会通过双写形成隐性共享模型。验收每服务一库不能只数数据库,而要看账号权限、跨库写、模式发布、权威源、备份责任、恢复演练和成本账单。跨域事务增加时还应重审聚合边界,而不是直接引入全局事务。详情见数据所有权方案对比。 选型记录要列出模块化单体、同实例隔离和独立实例三种方案的连接数、备份时长、恢复目标与月度成本,达到明确性能或合规阈值才迁移,收益消失时允许合并实例。 因此评审结论可能是“逻辑每服务一库、物理共享实例”,并附触发迁移的阈值,例如库存磁盘延迟、账务恢复目标或租户隔离要求,而不是写一个永久教条。
    • 追问 1:同实例会不会仍然互相拖垮?
    • 直接回答:会,所以还需连接池、资源配额、慢查询治理和备份窗口;隔离需求达到阈值时再迁独立实例。 物理隔离前先压测共享实例上的资源争抢,只有连接池、磁盘和备份窗口无法通过配额治理时才迁实例;这样能证明成本来自真实瓶颈而非架构偏好。
    • 追问 2:跨库事务怎么办?
    • 直接回答:先检查是否划错聚合或事务边界;真正跨上下文流程用状态机、事件、补偿和对账,不默认追求全局事务。
    • 追问 3:每服务一库后报表怎么做?
    • 直接回答:通过事件构建读模型、离线快照或受控数据平台,不让报表反向获得业务写权限。
  13. 问题(综合题):Shared Database(共享数据库)与 Shared Table(共享表)有什么本质差异?

    • 口述答案:Shared Database(共享数据库)描述多个边界使用同一数据库资源,风险大小取决于逻辑隔离和权限;Shared Table(共享表)则通常意味着多个服务把同一表当公共模型,尤其多写方时会直接破坏自治。订单和库存可以暂时位于同一实例,只要各自使用独立 Schema(模式)或表、独立账号、禁止跨边界写,并由明确所有者管理模式变更,这更像成本受控的模块化部署。若订单、履约、库存都能更新库存余额表,表字段、状态码、锁和事务就成为未版本化契约:任何一方改列都可能迫使全部同步发布,旁路写绕过聚合不变量,事故后也无法定位责任。治理时先审计真实写入身份和脚本,把所有库存修改封装到库存命令,影子比较新旧结果,再按调用方撤销写权限;跨域列表迁到 Read Model(读模型)。物理迁库可以后置,因为先改变所有权比先搬数据更重要。共享实例还需要资源配额、连接隔离与慢查询治理,避免一个报表拖垮交易。退出条件应量化为越权写为零、模式依赖清除、差异率达标、权限演练通过和回滚可执行。详情见共享库存表迁移演绎。 迁移期间旧写都要带应用身份和业务键,无法识别来源的写入直接报警;切断权限前先影子调用新命令并按数量、版本和状态比对,不能只比较接口返回是否成功。 若短期仍需共享表,应把跨域字段标为只读快照并禁止反向覆盖;数据库变更先兼容新增、迁移数据和消费者,最后收缩旧列,避免停机式一次切换。
    • 追问 1:数据库外键能跨上下文保留吗?
    • 直接回答:通常不应作为跨边界强耦合;可在同一模块阶段保留校验,但迁移前要改为稳定标识和业务一致性检查。 对共享表还应建立列级责任矩阵和变更冻结,先禁止新调用方,再替换现有写方;每撤销一个账号都做一次失败演练,确保隐藏脚本不会在月末批处理时重新出现。
    • 追问 2:只读联表有没有风险?
    • 直接回答:有模式、权限和资源耦合;临时分析可受控使用,在线核心查询应迁接口或读模型。
    • 追问 3:共享表短期无法拆怎么办?
    • 直接回答:先指定唯一写方、封装入口、增加写入审计和字段责任,冻结其他服务新增依赖,并设退出里程碑。
  14. 问题(综合题)legacy-bank-21 跨服务查询应如何选型?

    • 口述答案:跨服务查询先按业务用途分类,而不是统一选择某个聚合框架。单个订单详情、依赖三到五个上下文、要求接近实时并允许字段部分缺失时,可以采用 API(应用程序接口) Composition(接口聚合):并行调用所有者的批量接口,设置端到端超时预算、限制扇出,明确哪个字段未知以及是否可回源。高频列表、搜索、多维排序和运营报表更适合事件投影 Read Model(读模型),它按页面冗余字段,接受明确的秒级或分钟级延迟,支持幂等、版本检查、缺口补拉、重放和全量重建。大规模财务导出则使用离线快照,并记录数据截点。关键原则是查询方案不能改变 Data Ownership(数据所有权):读模型不可反向写,退款、库存预占和强制出库必须回权威模型重新校验。选择时比较新鲜度、吞吐、扇出数、尾延迟、部分失败、重建成本和团队运维能力。WMS(仓储管理系统)工作台可读投影并显示数据时间,点击高风险动作时携带当前版本回源。直接跨库 Join(连接)只适合受控临时分析,不应成为在线公共契约。方案还要提供降级路径:投影故障可单据回源,权威服务故障时不能用陈旧投影执行写操作。详情见跨服务查询方案。 评审时给每类查询写清最大数据年龄、最大扇出、分页边界和权限过滤位置;同一页面若拼接多个时点的数据,也要明确它不是原子快照,避免运营把时间差误判为错误。 还要按权限边界选方案:接口聚合可实时执行所有者授权,读模型则必须在投影时保存租户与可见范围,并在权限变更时支持撤销或重建,不能只优化性能。
    • 追问 1:接口聚合能否加缓存?
    • 直接回答:可以缓存稳定字段并标记时间,但涉及状态决策要回源;缓存不能掩盖下游无批量接口的问题。 选择结果应写进查询契约:单据详情失败时是否整页失败、列表投影延迟多久告警、离线报表以哪个业务时点截数。没有这些语义,同一数字会在不同页面产生争议。
    • 追问 2:读模型延迟时页面怎么展示?
    • 直接回答:展示数据时间与延迟状态,关键字段允许点单回源,缺失时显示未知而不是默认成功。
    • 追问 3:报表是否应该调用在线服务?
    • 直接回答:小量即时查询可以;大范围扫描应走读模型或离线快照,避免占用交易库和服务线程。
  15. 问题(综合题):API(应用程序接口) Composition(接口聚合)如何治理尾延迟和部分失败?

    • 口述答案:API(应用程序接口) Composition(接口聚合)的整体耗时由最慢依赖主导,依赖越多,至少一个请求变慢或失败的概率越高,因此不能把它理解成简单串联接口。设计时先定义页面总预算,例如 300ms(毫秒),再为连接、读取和聚合处理分配子预算;调用采用并行与批量接口,避免一页五十单放大为二百五十次请求。每个字段还要区分必需、可选和可缓存:订单基本信息失败时整页可失败,支付备注失败可显示未知,稳定仓库名称可读短期缓存。重试只针对幂等读取、限制次数并受总预算约束,不能在尾延迟时放大流量。返回契约显式携带数据来源、时间和部分失败,不把缺失值写成空字符串。对慢依赖使用隔离、限流和降级,同时监控扇出数、各依赖 P95(95 分位响应时间)与 P99(99 分位响应时间)、聚合成功率和连接池。若列表长期依赖五个以上上下文或吞吐高,应转 Read Model(读模型),聚合保留单据回源。WMS(仓储管理系统)从逐行调用改五个批量接口,再将列表迁投影,就是按成本逐步演进。任何降级都要保持“未知”语义,不能用默认值推动业务状态。详情见跨服务查询的数据演绎。 上线前用依赖超时、依赖返回旧版本和连接池耗尽三种故障演练,确认总预算到期能取消剩余请求、释放资源并返回可解释结果,监控同时区分业务失败与技术失败。 容量计算要把重试和页面并发计入下游放大倍数,聚合层设置每依赖并发上限;某个可选字段超过预算时立即舍弃,而不是占满线程拖慢必需字段。
    • 追问 1:并行调用就一定更快吗?
    • 直接回答:能降低串行累加,但会提高瞬时并发与连接压力;需批量、隔离和总预算,依赖过多仍应改读模型。 聚合层本身也要无状态或可水平扩容,禁止保存跨请求业务状态;请求结束后把依赖耗时、降级字段和数据时间写入观测信息,方便区分下游慢与聚合代码慢。
    • 追问 2:部分失败返回成功码合理吗?
    • 直接回答:可返回页面可用结果,但响应必须明确哪些字段失败及数据时间,不能把未知伪装为业务成功。
    • 追问 3:聚合层能写业务吗?
    • 直接回答:不应承载领域规则或跨库修改;它负责查询编排,业务命令仍进入对应所有者。
  16. 问题(综合题):Read Model(读模型)如何保证可用、可解释和可重建?

    • 口述答案:Read Model(读模型)是权威数据的派生投影,设计目标不是伪装强一致,而是让延迟与恢复可管理。首先为每个来源保存聚合标识、聚合版本、事件标识和更新时间;消费者以事件标识去重,对同一聚合拒绝旧版本,发现版本跳跃时暂停该实体更新并回源补拉。其次定义投影 SLO(服务等级目标),例如正常 P99(99 分位响应时间)延迟小于八秒,页面展示数据时间,关键操作回权威模型校验。第三,投影逻辑要可重放:事件保留期足够时从头构建,保留不足时先导入带版本的权威快照,再消费增量;模式升级采用新旧投影并行和校验后切换。第四,定期比较源聚合与投影的数量、状态和版本,缺口进入修复队列,不允许运营直接改读表掩盖问题。故障时可降级为单据回源、显示部分字段或暂停高风险操作。读模型可按搜索、报表和工作台需求冗余,却不能成为退款、扣库存或账务冲正的写源。验证包括投影延迟、版本缺口、重复率、重建耗时和源投影差异,且必须演练从空库重建而不是只写文档。详情见读模型与查询边界。 重建时记录快照截点和增量起点,双投影比对通过后原子切换查询别名;若重建耗时超过恢复目标,就缩小投影、增加分片或提高事件保留期,不能靠人工改表恢复。 可解释性还要求每个投影字段能追溯来源上下文、聚合版本和转换规则;运营发现异常时可定位到原事实,而不是面对一张无法说明生成过程的宽表。 查询恢复完成后再随机回源核对高风险单据,确认页面数据时间、版本和权威状态一致。
    • 追问 1:事件保留期不足怎么重建?
    • 直接回答:从权威源生成带版本和截点的全量快照,再从截点后的事件增量追平,最后做一致性校验。 投影模式升级采用新索引或新表并行构建,不在生产表上边消费边改含义;切换前随机抽样订单、库存和账务状态,再对总量与版本做全量校验,失败就保留旧读路径。
    • 追问 2:投影表能否人工修复?
    • 直接回答:可通过可审计的重放或补拉修复派生数据,不能直接修改业务事实;根因仍要在权威源或投影逻辑修复。
    • 追问 3:读模型是否每个页面一个?
    • 直接回答:按查询变化和成本权衡,可多个页面共享稳定投影,也可为高价值场景专建,避免万能宽表。
  17. 问题(综合题):CQRS(命令查询职责分离)何时值得用,何时是过度设计?

    • 口述答案:CQRS(命令查询职责分离)值得使用的前提是命令与查询确有不同模型、负载或变化原因。写侧围绕订单确认、库存预占、退款等意图保护不变量,查询侧需要跨域列表、搜索、排序和报表;当读流量远高于写、独立扩容有收益、页面可接受明确延迟,分模型能降低写模型污染。实施可以从同库不同对象开始,不必一开始就双库、MQ(消息队列)和 Event(事件) Sourcing(事件溯源);只有性能和隔离证据充分时才物理分离。代价是投影事件、延迟、重复乱序、模式兼容、重建、对账和值班,简单增删改查或低频后台页面使用后只会增加故障点。WMS(仓储管理系统)工作台适合 CQRS(命令查询职责分离),因为它读五域字段且可容忍秒级延迟;库存预占不适合读取投影后直接扣减,命令端必须回库存聚合重判。评估指标包括读写比例、查询扇出、尾延迟、投影延迟、重建时间和团队维护成本。若双模型长期同步修改、无独立扩容价值,可退回分模型同库或单模型。退出时先切查询、保留比对窗口,再停止投影和清理派生存储。详情见CQRS(命令查询职责分离)边界。 引入前必须比较批量接口、数据库视图和离线快照,只有读写模型确实不同且投影运维预算有人承担才采用;若投影事故成本长期超过查询收益,应主动简化方案。 我还会给方案设置退出指标:若一年内读写模型始终同步变更、读库没有独立扩容且投影事故占比上升,就合并模型或存储;可逆性是选型的一部分。
    • 追问 1:CQRS(命令查询职责分离)必须配合领域事件吗?
    • 直接回答:物理分离时通常需要变更传播,可用领域事件或变更捕获;逻辑分离同库时不一定需要异步事件。 决策记录还要注明命令端回源策略:任何由陈旧投影发起的操作,都必须携带业务版本并在写模型重新校验;否则读写分离会把延迟窗口转化为错误写入。
    • 追问 2:查询模型可否包含计算字段?
    • 直接回答:可以,只要公式、来源和更新时间可解释,并能从权威事实重建,不能成为反向业务规则。
    • 追问 3:如何退出 CQRS(命令查询职责分离)?
    • 直接回答:确认收益消失后切查询回简化模型,保留校验窗口,再停止投影和事件消费并清理派生库。
  18. 问题(综合题):CQRS(命令查询职责分离)与 Event(事件) Sourcing(事件溯源)为什么不能画等号?

    • 口述答案:CQRS(命令查询职责分离)关注命令模型与查询模型的职责分离,Event(事件) Sourcing(事件溯源)关注用事件序列作为聚合权威状态,两者可以组合但彼此独立。最常见的 CQRS(命令查询职责分离)仍可在关系型数据库保存当前写状态,再发布事件构建查询投影;不需要把全部历史事件作为唯一事实。Event(事件) Sourcing(事件溯源)则要求每次状态变化以不可变事件记录,当前状态通过重放获得,带来完整审计、时点回放和新投影能力,但同时要求事件语义长期稳定、快照策略、版本升级、删除与隐私合规、重放副作用隔离以及更高的调试门槛。支付账务重视不可变流水,但也不能因此随意把所有业务做事件溯源;账务分录本身可作为权威记录,订单仍可保存当前状态和审计日志。选择时要问是否真的需要任意时点重建、事件是否比当前状态更自然、团队能否承担长期契约演进。若只是列表性能,普通 Outbox(发件箱)加 Read Model(读模型)足够。验证 Event(事件) Sourcing(事件溯源)还要证明重放结果确定、外部副作用不会再次执行、快照与事件一致,并能处理事件版本升级。详情见CQRS(命令查询职责分离)方案表。 事件修订不能直接改变历史语义,通常通过上转器或补偿事件兼容;每次重放要固定代码与规则版本并输出状态摘要,否则同一历史得到不同结果就无法审计。 采用前应先为一个聚合做完整演练,验证十万事件重放耗时、快照损坏恢复和旧版本上转;任一无法自动化时,扩大到资金或库存核心域的风险都不可接受。
    • 追问 1:领域事件能直接作为事件溯源事件吗?
    • 直接回答:不一定。事件溯源事件必须完整重建聚合且永久兼容,跨域发布事件通常更稳定、更精简,二者可转换。 若团队无法回答事件如何上转、快照如何校验和十年后如何重放,就不应采用事件溯源;保存当前状态、审计流水和领域事件往往已满足追责与投影需要。
    • 追问 2:删除用户数据怎么办?
    • 直接回答:需预先设计加密擦除、标识脱敏和合规保留策略;不可变不等于可忽略隐私法规。
    • 追问 3:重放会再次付款吗?
    • 直接回答:绝不能。重放只重建内部状态,外部副作用通过端口隔离并在重放模式禁用。
  19. 问题(综合题):订单取消如何跨库存和履约边界实现,而不做级联改库?

    • 口述答案:订单取消是一个跨上下文业务流程,不是把三张表状态同时改回去。订单上下文先校验客户取消资格,用幂等键记录“取消处理中”,然后请求履约评估可撤销范围。履约根据未分配、已拣货、已复核、已出库等阶段作出自己的决定:未拣货可直接取消,已拣货需要回库复核,已出库则可能只能拒绝取消并转退货。库存只根据履约确认的数量和原预占流水释放,不接受订单直接把预占清零;释放命令幂等,已扣减流水不能走普通释放。支付若已确认,要进入退款流程;账务不删除分录,而是用冲正或退款分录保持审计。订单收集各上下文事实推进自身取消状态,超时进入未知态并查询原请求,长期失败进入补偿任务和人工工单。整个流程允许部分完成且每一步可解释,不宣称一个分布式事务回滚外部渠道和仓内实物。监控取消龄期、待回库数量、待释放预占、待退款和人工工单,并做订单、库存、履约、支付、账务对账。流程管理器拥有进度,不拥有各域事实;重试必须复用原幂等键。详情见取消命令数据演绎。 超时预算还要按仓内阶段区分:未拣货可自动重试,已拣货则先冻结后续出库并生成回库工单;取消完成前页面展示每一行结果,不能用全单成功掩盖实物仍在仓内。 客服和运营看到的不是一个笼统“取消失败”,而是每个订单行的库存释放、履约回库和退款进度;这种可解释中间态也是边界正确性的业务验收。 只有库存释放、履约关闭、退款或无需退款等终态均有证据时,订单才结束取消流程;缺一项就保留处理中并告警。
    • 追问 1:取消流程由谁编排?
    • 直接回答:可由订单上下文的流程管理器编排,但它只维护流程状态和发命令,不越权修改库存、履约或账务。 流程还要防止补偿反向覆盖新动作:取消等待期间若客服恢复订单,必须生成新版本并撤销旧补偿资格;迟到释放命令发现版本不符后只记录冲突,不能继续释放。
    • 追问 2:部分商品可取消怎么办?
    • 直接回答:按订单行和履约任务记录结果,释放对应预占并计算退款,订单保留部分取消状态而非全局二值状态。
    • 追问 3:消息乱序会不会先完成取消再收到出库?
    • 直接回答:按聚合版本、任务版本和合法状态机拒绝旧事实;冲突进入人工核对,不能用到达顺序覆盖。
  20. 问题(综合题):支付成功与账务入账为什么必须属于不同边界?

    • 口述答案:支付和账务处理的是不同事实与不变量。支付上下文回答交易意图是否被渠道确认,关心商户、渠道交易号、金额、币种、回调、查单、退款资格和未知结果;账务上下文回答经济事项如何形成不可变分录,关心会计日期、科目、借贷平衡、手续费、税、汇差、冲正和关账。支付成功不代表账务已经过账,账务过账也不应反向修改支付渠道状态。两者若共用一个“订单金额”和成功状态,手续费、跨币种和跨日处理会变得无法解释。正确做法是支付聚合确认后发布版本化事实,账务以事件标识幂等生成凭证,确保借贷合计相等;失败时事件可重试,凭证生成异常进入待入账队列和对账,不能重复扣款。退款在支付侧发起渠道交易,账务侧以新分录冲正原经济影响,不删除历史。订单只消费支付和履约事实判断客户承诺。验证要对渠道账单、支付交易、账务凭证和银行流水四方核对,监控支付已确认但未入账年龄,并为长期差异建立审批与人工修复。即使两者暂时同一部署,也应保留模型、表和状态边界。详情见支付账务上下文。 跨日或跨币种时,支付发生时间、渠道清算日和会计日期可能不同,必须分别保存;关账后迟到事实进入下一期间调整,不能回写已关账分录,这样才能解释每一分钱。 故障演练要覆盖支付事件重复、账务数据库停机和日切边界,证明重复事件不生成重复凭证、恢复后可按事件标识补记,并且关账规则不会被自动重试绕过。 财务验收还需抽查原交易到冲正的完整凭证链,确保审计可追溯。
    • 追问 1:支付和账务能部署在同一服务吗?
    • 直接回答:小规模可同一部署,但保留模型、表、状态和责任边界;合规或发布需求出现后再独立。 对账差异要按原因分类为渠道缺单、支付未知、入账漏记、金额不等和重复凭证,每类都有查询、补发、冲正或人工审批路径;只比较总金额会掩盖单笔错配。
    • 追问 2:入账失败要回滚支付吗?
    • 直接回答:通常不能自动回滚外部成功支付,应重试入账、对账和人工修复;确需退款要走新的业务交易。
    • 追问 3:账务分录能修改吗?
    • 直接回答:已过账分录原则上不可原地覆盖,错误通过冲正和新分录更正,保留完整审计链。
  21. 问题(综合题):遗留 WMS(仓储管理系统)如何按 Strangler Fig Pattern(绞杀者模式)迁移边界?

    • 口述答案:遗留 WMS(仓储管理系统)迁移不能先把大表复制成五个库,再希望边界自然出现。我会先从真实写路径和业务规则确定第一个目标能力,例如库存预占,并声明迁移期间旧系统仍是唯一权威。第一阶段为旧写增加应用身份、请求标识和流水审计,冻结新增旁路;新库存上下文通过 ACL(防腐层)理解旧批次、质检和状态码。第二阶段回放历史命令或接收影子流量,新模型只计算不影响业务,按仓库、货主、SKU(库存单位)、版本和结果比较差异。第三阶段在差异低于阈值后按低流量仓切换,路由保证某仓同一时刻只有一个写源,旧写被数据库权限和应用拦截;读流量暂可双读校验。第四阶段迁移报表到 Read Model(读模型)、补齐历史快照,稳定窗口后删除旧账号、代码和字段。每批都有暂停、单仓回切和数据回放方案,不能永久双写或双向变更捕获。成功标准是权威唯一、差异闭环、旧路径删除、超卖和待释放指标稳定,而不是新服务上线。迁移后的库存独立部署收益不足时,也可保留模块边界后合并部署。还要演练切换期间请求重复、进程崩溃和增量回放,证明回滚不是纸面方案。详情见遗留边界迁移。 每个仓的切换清单包含未完成预占、待释放流水、消息位点和库存快照;回切时先暂停该仓写入,单向回放新权威期间的增量并核对守恒,避免临时双主。 迁移方案还要明确旧系统何时只读、何时归档,以及审计查询保留多久;若旧库永久承担在线查询,新上下文仍会被旧模式和资源约束,迁移并未真正结束。
    • 追问 1:为什么优先迁库存而不是订单?
    • 直接回答:不是固定顺序;应选边界较清楚、收益可量化、依赖可控且能按仓回滚的能力,库存常满足但需具体评估。 切换期间的观测面板按仓展示新旧结果差异、条件冲突、待发布事件和请求延迟;负责人必须在场并拥有暂停权限,不能把不可逆切换交给无人值守脚本。
    • 追问 2:影子结果有副作用怎么办?
    • 直接回答:影子路径必须禁用消息、支付、打印和设备控制,只记录计算结果;外部副作用不可用于比对。
    • 追问 3:历史数据要一次搬完吗?
    • 直接回答:不必。先迁业务所需当前余额与未完成流水,再按截点补历史,保证增量事件和快照版本可衔接。
  22. 问题(综合题):线上订单和库存不一致,如何判断是数据错误还是模型认知滞后?

    • 口述答案:我会先区分“权威事实错了”和“其他上下文尚未认知最新事实”。先限定影响仓、租户、订单、SKU(库存单位)和时间窗,暂停高风险人工释放与自动补偿,保留数据库审计、消息位点、日志和快照。然后用订单号、预占幂等键、事件标识、聚合版本和 TraceId(链路标识)串联订单命令、库存余额与预占流水、Outbox(发件箱)、消息投递和订单消费记录。库存上下文的余额和流水是数量权威:若数量守恒且版本连续,而订单仍显示待预占,多半是事件漏发、积压、乱序或投影延迟;应补发或重放并修复消费者。若库存出现负数、流水与余额不平或多个应用写同表,则是权威事实或所有权被破坏,要立即收回越权写、按审计恢复正确值。人工修复必须通过工单和幂等命令,记录前后状态,不做无痕改表。修复后全量核对数量守恒、事件版本缺口、源投影差异和越权写,并回归超时、重复、乱序场景。页面要把“未知”和“失败”分开,避免认知滞后触发重复预占。最后将根因落到边界、发布或消费机制,不把一次补数据当完成。详情见边界漂移排查。 若是投影滞后,修复目标是恢复版本连续并降低积压;若是权威错误,目标是恢复不变量、收回旁路和补齐约束。两类问题的止血与复盘不同,不能混为一次补数据。 事故沟通中要分别报告权威数据影响和展示滞后影响,避免把所有订单都按资损处理;恢复后以抽样单据和全量聚合校验共同证明,而不是只看消息积压归零。
    • 追问 1:如何快速止血?
    • 直接回答:按仓或租户关闭相关写入口、冻结自动补偿、保留查询与证据,避免全站停机和继续扩大差异。 保存现场时要记录消息消费组位点和数据库事务时间,避免补发后原积压再次到达造成二次处理;修复脚本先在脱敏快照演练,并以请求键保证重复执行无副作用。
    • 追问 2:能否以订单状态为准修库存?
    • 直接回答:不能。订单只持有流程认知,库存余额和流水才是数量权威;需先查明事件和命令链。
    • 追问 3:补发事件会不会重复处理?
    • 直接回答:会出现重复投递,因此消费者必须按事件标识幂等、按聚合版本拒绝旧事件。
  23. 问题(综合题):什么时候应该把两个微服务重新合并?

    • 口述答案:服务合并不是架构倒退,而是重新校准部署收益。判断时分别看逻辑边界和物理边界:两个 Bounded Context(限界上下文)可以继续保留独立语言、模块、表和写权限,但不一定要跨网络部署。若连续数月大多数版本同步发布、由同一团队值班、没有独立扩容或合规隔离需求,跨服务调用、最终一致和补偿却持续制造延迟与事故,就应评估合并。先用发布关联度、调用 P95(95 分位响应时间)、补偿次数、故障爆炸半径、CPU(中央处理器)差异和团队交接量建立基线,再在模块化单体原型中保持依赖方向和契约测试,比较本地编排后的性能与失败率。灰度合并部署时不能把数据表和内部实体混成一团,仍通过模块接口访问;消息可按可靠性需求改为进程内事件或继续保留 Outbox(发件箱)。若履约需要按仓独立扩容、支付有安全隔离或不同团队独立发布,合并可能扩大故障域,则应修正聚合或交互而非合并。合并后保留可再拆的逻辑边界,并删除不再需要的网络治理和重复存储。验证周期要覆盖业务峰值、发布和故障演练,避免只比较本地压测。详情见回迁条件与数据演绎。 合并决策还要检查安全、数据保留和容灾目标能否共享;灰度期间可双跑新旧部署但只保留一个写源,以请求键比对结果,稳定后删除旧路由与告警,避免两套成本。 合并后的第一个发布周期仍保留原服务级指标标签和回切制品,确认故障定位没有退化;若资源争抢或发布冲突重新出现,应按证据恢复隔离而非坚持合并。
    • 追问 1:合并是否要合库?
    • 直接回答:不必同步进行。可先合并部署仍保留独立表或 Schema(模式),验证收益后再评估存储简化。 合并后的容量模型按整体峰值重新计算,防止一个模块的批量任务抢占交易线程;必要时仍保留线程池、连接池和资源舱壁,进程合并不代表故障隔离全部取消。
    • 追问 2:本地调用会不会绕过契约?
    • 直接回答:通过模块可见性、架构测试和接口保持边界,禁止直接引用内部实体与仓储实现。
    • 追问 3:合并后还能独立扩容吗?
    • 直接回答:不能按原服务独立扩容,因此只有独立扩容价值低时才合并;逻辑边界保留可支持未来再拆。
  24. 问题(综合题):跨境物流轨迹上下文如何与订单、履约划界?

    • 口述答案:跨境物流轨迹不应只是订单表上的一串状态。履约上下文负责仓内包裹创建、出库和向承运商交接;轨迹上下文负责接收多承运商节点、统一地点与状态语义、处理乱序重复、识别最终态并向客户查询提供时间线;订单只关心是否满足承诺和是否需要客服介入。外部承运商模型不可控,应通过 ACL(防腐层)校验运单号、签名和租户,把不同渠道码翻译成内部 Published Language(发布语言)。轨迹事件携带承运商事件标识、业务运单号、事件时间、接收时间和状态版本;消费者不能简单按到达时间覆盖,因为跨境节点可能延迟数天。最终态之后的旧中间态应拒绝,冲突进入人工核对。查询列表可走 Read Model(读模型),但改址、拦截和理赔必须回履约或轨迹权威模型确认。若轨迹仅服务一个低量承运商,可先作为履约模块;当多承运商接入、吞吐和独立发布显著时再拆。验证包括重复率、乱序率、未知码、最终态回退、轨迹延迟和承运商账单对账。轨迹缺失时页面必须显示数据时间和未知状态,不能把无更新解释为运输正常。详情见限界上下文和 ACL(防腐层)。 同一运单的状态机允许海关扣留后恢复,却禁止签收后回退运输中;无法映射的新节点保留原文并进入规则队列,人工确认后发布新映射版本,历史可重算但原始报文不可丢。 轨迹上下文还要按承运商记录状态映射版本,规则升级后能解释历史页面为何不同;客户承诺超期由订单判断,轨迹只提供事实和异常标签,不能替订单赔付。
    • 追问 1:轨迹事件时间和接收时间用哪个排序?
    • 直接回答:以承运商业务序列和合法状态机为主,事件时间辅助;接收时间只说明到达,不代表业务先后。 承运商重推历史轨迹时按外部事件标识去重,同时以内部运单版本检查;对方无法提供稳定标识时,用运单、节点、地点和事件时间组合去重并保留碰撞审计。
    • 追问 2:订单要保存全部轨迹吗?
    • 直接回答:不需要。订单保存影响承诺的摘要事实,完整时间线归轨迹读模型,避免模型膨胀。
    • 追问 3:第三方长期超时怎么办?
    • 直接回答:限速重试、主动拉取、缓存最近结果并展示数据时间,超龄进入告警和人工查询,不能伪造已送达。
  25. 问题(综合题):Runner(执行器)调度属于哪个边界,任务数据由谁拥有?

    • 口述答案:Runner(执行器)调度通常是通用或支撑能力,它拥有任务定义、触发时间、分片、租约、执行尝试、超时和调度历史,但不拥有库存释放、账务入账或物流补拉的业务最终状态。业务上下文创建带幂等键的任务意图,Runner(执行器)只负责“至少触发一次”和记录执行结果;执行端回到对应 Aggregate(聚合)校验当前业务状态,因此重复执行不会重复释放或入账。租约到期后任务可能被另一节点接管,旧节点仍可能继续运行,故需 Fencing Token(栅栏令牌)或业务版本拒绝旧执行者提交。任务成功表示执行函数返回并被记录,不等于外部支付、设备或业务流程一定完成;未知结果要由业务查询和补偿。若把业务状态塞进调度表,所有业务会依赖 Runner(执行器)模式并失去所有权。小系统可在模块化单体内用定时任务,只有任务量、隔离、分片和统一治理收益明确时才独立服务。监控租约冲突、重复执行、超龄任务、业务幂等拒绝和死任务,并由业务所有者处理不可自动恢复的结果。共享调度平台还要按租户和任务类型隔离队列、凭证、配额和审计。详情见数据所有权。 调度恢复时先查询业务幂等结果再决定重跑;对付款、打印面单和设备控制等外部副作用,执行器保存请求键和查询入口,结果未知时宁可转人工也不能换新键再次调用。 业务方删除或暂停任务时也要走版本化命令,避免调度节点拿着旧快照继续执行;执行前再次校验启用状态和 Fencing Token(栅栏令牌),失租结果不得提交。
    • 追问 1:任务表状态能作为业务成功依据吗?
    • 直接回答:不能。它只表示调度尝试,业务成功必须查询权威上下文的状态和流水。 任务定义还要记录所属业务、最大执行时长、重试上限和人工联系人;超过业务截止时间后不再机械重试,而是让所有者决定跳过、补执行或生成补偿交易。
    • 追问 2:如何避免重复执行?
    • 直接回答:调度层租约减少重复,业务层幂等键、版本和条件更新承受重复;不能只依赖锁。
    • 追问 3:Runner(执行器)要不要每业务一个?
    • 直接回答:不必。可共享调度平台但任务、队列、配额、凭证和审计要隔离,业务规则仍留在各上下文。
  26. 问题(综合题):IoT(物联网)报警风暴治理如何体现领域边界和数据所有权?

    • 口述答案:IoT(物联网)报警风暴中,设备遥测、规则判定、告警事件、通知触达和工单处置是不同模型。设备上下文拥有设备身份和原始遥测;规则上下文根据设备、规则版本和时间窗判断“阈值已违反”;告警上下文拥有去重、聚合、优先级、静默、恢复与确认状态;通知只负责渠道投递,工单负责人员处置。不能让短信回执反向把告警标记已恢复,也不能让工作台直接改遥测。Event(事件) Storming(事件风暴)会暴露“十秒内一万条同类事件如何聚合”“严重告警能否穿透静默”等策略,据此形成边界与聚合键,例如设备组、规则和时间窗。告警风暴时先在规则或告警边界做去重、背压和优先级,保留严重告警;通知按租户配额降级。Read Model(读模型)展示聚合结果和数据时间,高风险设备控制仍回设备权威模型。若规模小,可把规则与告警合并模块;当规则计算和通知吞吐独立扩容时再拆。验证包括原始事件可追溯、聚合前后数量、严重告警丢失为零、静默与恢复状态合法、通知失败不改变告警事实。人工确认也只改变处置状态,不能篡改告警发生事实。详情见事件风暴与聚合。 演练可输入一分钟十万条普通告警和一百条严重告警,验证普通告警按设备组与窗口聚合、通知受配额控制,严重告警仍穿透静默;风暴结束后核对原始、聚合和通知数量。 数据保留也按边界处理:原始遥测按时序策略归档,告警事实按审计期保留,通知记录按渠道规则保存;不能为了一个工作台把三类数据复制成永久宽表。
    • 追问 1:告警去重键怎么设计?
    • 直接回答:结合租户、设备或设备组、规则版本、告警类型和时间窗,避免只按文案导致不同风险误合并。 告警聚合要保存首次、最近发生时间和累计次数,使降噪后仍可判断持续恶化;恢复事件只有规则重新满足正常窗口才产生,通知成功与人工已读都不能替代恢复判定。
    • 追问 2:通知失败是否重开告警?
    • 直接回答:不应。告警事实与通知投递分离,通知重试或换渠道,告警状态由规则和处置决定。
    • 追问 3:原始遥测要不要都进告警库?
    • 直接回答:不必复制全部,可保存证据引用和必要快照,完整遥测归设备或时序数据所有者。
  27. 问题(综合题):模块化单体如何落实 DDD(领域驱动设计),避免成为“大泥球”?

    • 口述答案:模块化单体不是把所有代码放一个进程就结束,而是在不支付分布式成本的前提下严格保留 Bounded Context(限界上下文)。每个上下文有独立包或构建模块、公开应用接口、内部领域模型和持久化实现;模块间只依赖公开契约,架构测试禁止直接引用内部实体和仓储。数据可以位于同一实例,但使用独立 Schema(模式)或明确表所有者,其他模块不能直接写;跨模块查询通过接口或只读投影。聚合不变量仍由 Aggregate Root(聚合根)保护,领域事件可进程内分发,需要可靠异步时写 Outbox(发件箱)。发布虽然同一制品,但变更影响可通过模块测试、契约和代码所有者控制。运行指标按模块标记,事故能定位责任。相比过早微服务,它避免网络延迟、分布式事务、契约部署和多套值班;当库存需要独立扩容、履约需要独立发布或账务要求隔离时,清晰模块可沿接口平滑拆出。若模块间任意调用内部类、共享万能数据访问层和跨表事务,它就会退化为大泥球。验收看依赖规则、越权写、模块变更关联度、可独立测试与运行观测,而不看包名数量。详情见限界上下文与回迁条件。 持续集成应扫描禁止依赖和跨模块数据访问,模块测试只用公开接口;跨边界本地事务必须被白名单记录。这样未来拆进程时主要变化是传输与可靠性,而不是重新发现责任。 模块所有者还要独立维护领域测试和故障场景,公共启动工程只负责装配;这样单体发布失败时仍能定位是哪个模型或迁移脚本,而不是所有团队共同背锅。
    • 追问 1:同一事务跨模块可不可以?
    • 直接回答:应默认禁止以保护未来边界;确有强不变量时记录例外,并评估是否说明聚合或上下文划分错误。 模块数据库迁移也按所有者分目录和版本执行,禁止一个公共脚本任意改所有表;回滚时以模块为单位验证,不让一次索引变更迫使无关领域同时停机。
    • 追问 2:模块间事件一定异步吗?
    • 直接回答:不一定。进程内可同步通知,只有可靠解耦和削峰需求时异步;业务事实语义不依赖传输方式。
    • 追问 3:如何保证未来可拆?
    • 直接回答:稳定接口、禁止内部依赖、独立数据所有权、契约测试和不依赖本地调用语义是关键,不承诺零成本拆分。
  28. 问题(综合题):团队和组织结构如何影响 Bounded Context(限界上下文)与服务边界?

    • 口述答案:业务模型决定理想的逻辑内聚,组织能力决定边界能否被真实承担。一个服务要独立存在,团队需具备需求理解、开发、测试、发布、监控、值班、数据修复和契约协商的端到端责任;若六人团队维护二十个服务,每个服务都无人真正拥有,所谓自治只是部署数量。Conway’s Law(康威定律)说明沟通结构会映射到系统,但不是要求按当前部门机械拆分。可以先建立 Bounded Context(限界上下文)和模块接口,借此调整协作,再在团队能独立值班时拆部署。Context(上下文) Map(映射接口)还要写明 Customer-Supplier(客户方-供应方)关系和版本责任,避免上游团队单方面改契约。评估组织成本时统计跨团队需求等待、同步发布比例、事故转派次数、值班覆盖和平台自助能力。若库存和履约由同一团队、长期共同发布且没有独立负载,合并部署更诚实;若账务有专职团队与合规责任,独立边界更有价值。组织变化时模型不必立刻重画,但责任人、契约和运行所有权必须更新。任何无人值班、无人批准数据修复的服务都不应被视为自治。详情见上下文映射与组织治理。 组织评审还要测算节假日值班覆盖、跨团队接口等待和平台自助率;若每次故障都需五个团队拉群才能定位,边界或观测责任仍不完整,调整组织时也不能按汇报线频繁重拆模型。 组织调整不能让数据库和告警突然无主,交接清单要覆盖数据修复权限、契约消费者、容量和未决事故;责任交接完成前不以新的汇报关系宣布边界完成。
    • 追问 1:一团队可以拥有多个上下文吗?
    • 直接回答:可以,尤其小规模阶段;关键是各上下文语言和数据责任清晰,团队容量能承担运行责任。 团队指标不能只看需求数量,还要看独立发布成功率、事故自恢复和契约破坏次数;服务数量增加但跨团队等待没有下降,说明组织与边界并未真正解耦。
    • 追问 2:一个上下文能由多个团队共同维护吗?
    • 直接回答:可以但风险高,需要明确主责、代码所有权和决策机制;否则模型容易分裂和契约漂移。
    • 追问 3:平台团队是否拥有业务数据?
    • 直接回答:通常不拥有。平台提供运行能力和治理工具,业务上下文仍对语义、质量和修复负责。
  29. 问题(综合题):请完整讲述一次 WMS(仓储管理系统)边界重构项目。

    • 口述答案:项目背景是订单、库存、履约和财务共用大表,多个应用直接更新状态,峰值库存冲突、取消后未释放和报表慢查询频发。我们没有先拆服务,而是选三十个异常订单做 Event(事件) Storming(事件风暴),从命令、事实、策略和热点识别出订单承诺、库存守恒、仓内履约、支付交易、账务借贷五个 Bounded Context(限界上下文)。先在单体内重构模块:库存以仓库、货主、SKU(库存单位)和属性为聚合键,用条件更新、版本和幂等流水保证可售非负;订单、履约撤销库存表写权限,改走命令和 Domain(领域) Event(事件)。运营工作台从跨表查询迁 Read Model(读模型),高风险动作回权威模型校验。迁移采用审计、影子执行、按仓切写和差异阈值,保留单仓回切;外部支付通过 ACL(防腐层)处理验签、状态翻译和未知结果,账务以不可变分录独立入账。库存热点和履约发布节奏出现后才拆独立部署,低价值通知继续复用平台。我们监控越权写、负库存、待释放年龄、事件缺口、投影延迟和同步发布率,并保留对账与人工工单。最终正确性由权威流水证明,架构收益由故障范围、交付效率和总拥有成本数据证明。详情见WMS(仓储管理系统)完整边界推导。 我们也保留失败经验:早期同步串联五个模块导致取消链超时,后来改为流程状态、未知态查询和补偿;这说明成果不是拆出服务,而是建立可解释、可恢复且可回迁的边界。 回答项目结果时我会给出观察周期和反例,不只报一个百分比:大促期间热点冲突如何处理、一次消息漏发如何恢复、哪些低价值模块最终没有拆,体现真实取舍。
    • 追问 1:最大难点是什么?
    • 直接回答:不是技术搬库,而是让各方承认同一“订单状态”包含不同语义,并收回历史旁路写权限。 项目结果还应报告基线与观察窗口,例如越权写从多少降到零、投影延迟和补偿率如何变化;无法用同口径前后对比时,只能说完成迁移,不能宣称架构收益。
    • 追问 2:如何证明没有超卖?
    • 直接回答:数据库条件更新保证提交底线,余额与预占、扣减、释放流水持续对账,负库存和守恒差异报警。
    • 追问 3:项目有什么反思?
    • 直接回答:边界要用运行数据持续校准;不是所有模块都值得拆,服务收益消失时应敢于合并。
  30. 问题(综合题):如何评审一份 DDD(领域驱动设计)与微服务边界方案是否可落地?

    • 口述答案:我会从业务证据、模型、不变量、数据、交互、运行和退出七层评审。业务证据层要求有真实事件时间线、异常样本、业务专家和未决热点,不能只有服务名称。模型层检查 Subdomain(子域)、Bounded Context(限界上下文)、Ubiquitous Language(通用语言)与 Context(上下文) Map(映射接口),同词异义是否显式翻译。不变量层逐个列出 Aggregate(聚合)及原子规则,说明跨聚合为什么能最终一致。数据层要求每类事实有唯一写入方、权限与修复责任,Database per Service(每服务一库)选择有成本依据。交互层检查命令幂等、事件版本、重复乱序、超时未知、ACL(防腐层)、读模型和关键动作回源。运行层要求容量、延迟、投影 SLO(服务等级目标)、对账、告警、降级、人工兜底和团队值班。退出层必须说明不拆、分步迁移、回切、合并服务和删除旧路径的条件。WMS(仓储管理系统)方案还要用取消、部分出库、重复支付和库存冲突逐步演绎,证明不是模板。最终用越权写为零、不变量成立、契约可演进、故障范围和总拥有成本验收,而不是用服务数或技术名词验收。评审记录还要保存前提、负责人、复核日期和反例。详情见边界漂移与方案评审。 上线批准前随机抽取正常、重复、超时、乱序、部分成功和人工修复六类场景逐步走查,并执行权限、投影重建与回切演练;任何依赖“不会发生”的分支都要变成可观测状态和明确责任。
    • 追问 1:评审时最危险的红旗是什么?
    • 直接回答:多服务写同一数据、跨服务大事务、没有未知态、读模型反向写和无人承担修复,任何一项都足以暂停拆分。
    • 追问 2:方案图很完整但没有数据怎么办?
    • 直接回答:要求补量级、版本、超时、并发、失败率和成本阈值;无法量化时先做模块与观测,不批准物理拆分。
    • 追问 3:如何验证方案不是纸面设计?
    • 直接回答:用真实异常单走查、并发与故障演练、权限审计、影子比对、投影重建和回切演练共同验证。

5. 迁移闭环与复习清单

  • legacy-k-3.3-q1 已在 2.7 的基础题升级:从业务事实、模型与成本推导服务边界。
  • legacy-k-3.3-q2 已在 2.2 的原理题升级:Bounded Context(限界上下文)隔离同词异义和模型污染。
  • legacy-k-3.3-q3 已在 2.7 的项目题升级:库存与订单是否拆分取决于规模、规则和运行收益。
  • legacy-bank-03 已在综合题 03 闭环,保留服务拆分题意并扩展五类边界和迁移验收。
  • legacy-bank-20 已在综合题 12 闭环,明确 Database per Service(每服务一库)不是每服务一实例。
  • legacy-bank-21 已在综合题 14 闭环,对比 API(应用程序接口) Composition(接口聚合)、Read Model(读模型)和离线快照。
  • 旧表 3 已由 2.1、2.2、2.4、2.5 的概念与失败边界表吸收,不再停留于术语定义。
  • 能从业务事实解释 Domain(领域)、Subdomain(子域)和 Bounded Context(限界上下文),不从服务名反推。
  • 能用 Event(事件) Storming(事件风暴)还原正常流、失败流、人工决策和未知结果。
  • 能按不变量划 Aggregate(聚合),说明 Aggregate Root(聚合根)的唯一修改入口。
  • 能画 Context(上下文) Map(映射接口),解释 ACL(防腐层)和上下游契约责任。
  • 能为订单、库存、履约、支付、账务指定唯一权威源、幂等键、状态机、补偿和对账。
  • 能对比 Database per Service(每服务一库)、同库不同 Schema(模式)、共享表与迁移阶段。
  • 能为跨服务查询选择 API(应用程序接口) Composition(接口聚合)、Read Model(读模型)或离线快照。
  • 能说明 CQRS(命令查询职责分离)的延迟、回源、重建和退出条件。
  • 能按影响、止血、证据、根因、修复、回归、复盘排查边界事故。
  • 能说明模块化单体优于微服务的条件,以及服务重新合并时如何保留逻辑边界。