Skip to content

应用架构:从能力责任推导系统边界与协作契约

统一订单中心项目上线前,团队发现它已经承接了商品查询、会员等级、优惠计算、库存锁定、支付回调、积分发放和客服查询。它看起来像全渠道核心,实际上成为了一个谁都能调用、谁都能要求修改的“业务抽屉”。

应用架构要阻止的不是系统数量增加,而是责任没有边界。它需要回答:哪个应用承载哪项业务能力,哪个系统拥有哪类事实,系统之间通过什么契约协作,发生故障时谁负责恢复,以及旧应用如何逐步退出。

应用边界由能力、数据责任、团队和交互方式共同决定

应用不是系统清单,而是责任组合

应用组合至少要同时描述五个维度:

维度需要回答的问题
能力这个应用承载哪些稳定业务能力
数据它可以创建、修改、读取哪些事实
契约它向谁提供什么接口或事件,版本如何演进
运行它需要怎样的可用性、性能、隔离和发布节奏
责任哪个团队对功能、数据、值班和生命周期负责

只列出“商城、订单、库存、会员”四个系统,无法判断它们是否重复写库存、是否拥有不该拥有的规则,也无法判断一个新服务应该放在哪个边界内。

从能力和事实推导边界

应用边界通常由多个信号共同决定,而不是由类图或数据库表单独决定:

  1. 业务规则内聚:必须一起判断的规则尽量在同一责任边界内。
  2. 数据修改责任:同一业务事实的最终修改权只有一个主人。
  3. 一致性范围:必须原子成立的状态变化不要轻易跨边界。
  4. 变化节奏:不同发布节奏和生命周期的能力可以隔离。
  5. 团队责任:边界需要与真实的开发、运维和决策责任匹配。
  6. 故障影响:非核心能力不应把核心交易拖入同一故障域。

这些信号可能互相冲突。订单创建和库存预占在业务上有关联,但不代表必须放在同一个服务;库存预占的事实责任和高并发约束可能要求独立边界,两个边界再通过明确的同步接口和事件协作。

全渠道应用责任地图

应用边界负责的能力和事实明确不负责主要协作
渠道应用展示、购物车、渠道交互和编排企业商品、会员、库存和订单事实API、BFF、查询视图
会员中心客户身份、等级、权益账户订单状态、库存扣减客户查询、权益校验、会员事件
商品中心企业商品、类目、渠道映射渠道页面、实时库存商品 API、商品变更事件
订单管理订单身份、交易状态、价格快照直接修改实物库存、履约节点库存库存、支付、履约
库存平台可售视图、预占、释放、确认和调整记录支付结果、订单业务有效性同步预占、库存事件
履约系统选节点、拆包裹、发货、签收、异常改变订单交易规则订单事件、库存查询、物流接口
分析平台历史事实、指标和模型交易裁决、回写核心状态CDC、业务事件、批处理

“不负责”同样重要。它限制了应用边界,防止为了一个临时需求把会员等级、库存判断或订单状态重新复制到另一个系统。

模块化单体还是微服务

TOGAF 定义责任和演进目标,不会直接规定部署单元。应用架构应根据边界稳定性和运行收益选择实现方式。

条件模块化单体更合适独立服务更合适
业务边界规则仍在快速探索边界和数据责任稳定
一致性大量操作必须同一事务服务内事务、服务间可接受最终一致
团队一个团队统一交付多团队需要独立发布和负责
扩缩容流量模式相近某能力需要独立扩展或隔离
故障共享运行风险可接受需要独立故障域和降级
运维平台能力有限已具备发布、观测和治理基础

先用模块化单体验证业务模型,不是保守;直接拆微服务也不等于架构成熟。判断标准是边界是否已经提供足够的独立价值,能够抵消网络、部署、测试、观测和一致性成本。

下单链路中的同步与事件

应用协作要按业务语义选择同步调用或事件,而不是把所有调用改成异步。

text
客户端
  -> 订单管理:提交订单
  -> 库存平台:请求预占,等待成功/失败结果
  -> 支付平台:创建支付意图
  <- 订单管理:返回待支付订单

支付平台
  -> 订单管理:支付结果回调(可能重复、延迟、乱序)
  -> 订单管理:校验状态并记录订单已支付
  -> 事件总线:发布 OrderPaid
       -> 库存平台:预占转确认
       -> 履约系统:创建履约计划
       -> 会员中心:发放交易权益

同步接口适合需要立即决定是否继续的操作,例如库存预占;事件适合传播已经发生的事实。两者都必须设计幂等、超时、重试和补偿,但失败语义不同:同步调用要给当前请求明确结果,事件消费失败则要可重放并避免重复副作用。

契约首先表达责任

库存预占接口不应只定义 URL 和字段,还要说明谁可以调用、谁做最终判断、请求重复时返回什么、超时后订单处于什么状态。

yaml
operation: reserve_inventory
owner: inventory-platform
caller: order-management
idempotency_key: order_id + reservation_attempt
success: reservation_id, expires_at
business_failures:
  - OUT_OF_STOCK
  - LOCATION_NOT_SERVABLE
  - RESERVATION_LIMIT_EXCEEDED
timeout: order remains pending-reservation and can be retried
side_effect: publish InventoryReserved only after durable state is committed

事件契约也要声明:事件代表已发生事实、唯一 ID、发生时间、业务主键、Schema 版本、重复和乱序处理、敏感字段、保留时间,以及消费者无法处理时的重放路径。

状态机比状态字段更重要

订单状态不能由多个系统直接写字符串。至少需要定义合法转移和前置条件:

text
待预占 -> 待支付 -> 已支付 -> 履约中 -> 已完成
   |        |          |          |
   v        v          v          v
预占失败   已取消     退款中     履约异常

支付成功回调到达时,如果订单已经因超时取消,系统不能简单把状态改成已支付。它需要依据状态机和业务补偿规则决定是否重新激活、挂起人工处理或发起退款。状态机是应用架构与业务规则的交界面,不能只留在某个 Controller 的条件分支里。

读模型不是事实副本

客服需要跨渠道查询订单,首页需要快速展示库存,分析平台需要沉淀历史。它们可以建立读模型,但读模型必须声明:

  • 来源事实和最后更新时间。
  • 允许的最大延迟。
  • 是否允许展示给客户。
  • 是否可以被用户操作反向修改。
  • 重建方式和数据丢失后的恢复方式。

读模型无法参与交易裁决时,界面必须表达“数据暂时不可用”或采用保守策略,而不能把过期数据伪装成实时事实。

遗留系统如何退出应用组合

应用架构还要描述生命周期:引入、共存、迁移、冻结、归档和下线。

生命周期阶段需要保留的证据
引入为什么新应用能补足能力差距,谁承担责任
共存哪个范围由谁写入,数据如何对账
迁移流量、数据、配置和权限如何切换
冻结旧系统是否停止新功能和新数据入口
归档历史查询、审计和恢复如何满足
下线依赖、任务、凭证和访问权限是否清退

旧系统仍然能够查询,不代表它还应该拥有写入权。生命周期状态必须与实际流量和责任一致。

应用架构的最小交付物

  • 基线应用组合与能力映射。
  • 目标应用责任和“不负责”边界。
  • 数据权威关系与读模型来源。
  • 同步 API、事件和批处理契约。
  • 关键业务状态机和异常转移。
  • 质量属性场景与故障隔离策略。
  • 应用生命周期、迁移范围和退出条件。
  • 关键取舍的 ADR 与复审触发条件。

常见失效方式

  • 按数据库表或技术分层拆服务,业务责任被拆散。
  • 订单服务变成所有跨域规则的集中入口。
  • 事件只表达“请某服务做某事”,却没有事实语义和幂等规则。
  • 读模型没有时效标记,过期数据继续参与交易判断。
  • 共享数据库被多个服务直接写,接口只是表的另一种外壳。
  • 只设计新应用,不设计旧应用的冻结、归档和退出。

能力验证

  • 能从业务能力、数据责任、一致性和故障域推导应用边界。
  • 能说明某项能力为什么适合模块化单体或独立服务。
  • 能为同步接口和业务事件写出不同的失败语义。
  • 能用状态机约束重复、乱序和逆向状态变化。
  • 能区分权威事实、读模型和分析副本。
  • 能把应用迁移拆成共存、切换、冻结和退出证据。

总结

应用架构不是把系统画成更多方框,而是把能力和事实责任落实到可以被团队拥有、被契约约束、被故障隔离、被独立演进的应用边界上。好的应用组合会让后端团队更清楚“谁可以改什么、谁必须通知谁、失败后谁负责恢复”,而不是只增加服务数量。

别急,先让缓存热一下。