Day32-06|13.3.1 业务逻辑层组件设计:用清晰职责承载稳定业务能力

对应教材:《系统架构设计师教程(第2版)》第13章“层次式架构设计理论与实践”13.3“中间层架构设计”中的 13.3.1“业务逻辑层组件设计”。本文面向软考高级系统架构设计师,重点学习业务逻辑层组件的职责划分、接口设计、组件协作、事务边界、依赖控制、复用和测试方法。

一、教材定位与学习目标

在层次式架构中,业务逻辑层是连接表现层和数据访问层的核心中间层。它把用户请求转换为可执行的业务用例,组织领域对象和基础设施完成业务操作,并向不同终端提供一致的业务能力。如果业务逻辑层设计不当,系统就会出现两种典型问题:一是表现层“过胖”,页面、控制器和接口中散落着业务规则;二是业务层“过胖”,所有规则、流程、查询和外部调用都集中在少数巨大组件中,导致修改风险和测试成本不断增加。

业务逻辑层组件设计的重点不是简单地创建几个 Service 类,而是识别业务责任、建立稳定边界、控制依赖方向,并让组件具备清晰的输入、输出、事务和错误语义。考试中常见题型包括:给出一个复杂业务场景,要求划分业务组件;分析组件耦合和职责混乱的原因;设计业务服务接口和事务处理;说明如何提高复用性、可测试性和可维护性。

今天的学习目标:

  1. 理解业务逻辑层在层次式架构中的定位和职责。
  2. 区分应用服务、领域服务、业务组件和基础设施适配器。
  3. 掌握按业务能力、变化原因和一致性边界划分组件的方法。
  4. 设计接口、事务、异常、依赖和生命周期,避免组件失控。
  5. 能够结合案例题和论文说明组件设计如何支撑业务演进。

二、业务逻辑层的职责边界

1. 业务逻辑层应该做什么

业务逻辑层通常负责用例编排、业务规则执行、权限和状态校验、事务边界、领域对象协作、数据转换、外部服务协调和业务异常表达。它接收表现层传入的命令或查询,完成必要的业务处理,并返回结果或明确的业务状态。

例如“提交采购申请”这一用例,业务层可以负责检查申请人权限、校验预算、创建申请、确定审批路线、保存状态和发布事件。它不负责页面按钮如何排列,也不负责数据库连接字符串和具体 SQL。外部审批系统的协议细节应由适配器隐藏,业务层只依赖代表业务意图的接口。

2. 业务逻辑层不应该做什么

业务层不应承担页面布局、HTTP 状态码、浏览器跳转、桌面控件操作或具体数据库表结构。它也不应成为所有技术能力的“万能入口”,把缓存、文件、消息、日志和第三方SDK调用散落在每个方法中。技术细节应通过接口和基础设施实现隔离。

3. 与其他层的关系

层次 主要职责 与业务逻辑层的关系
表现层 请求接收、展示、交互和基础校验 调用应用服务,不决定核心规则
业务逻辑层 用例编排、规则、状态、事务和协作 系统业务行为的主要承载者
数据访问层 持久化、查询、映射和连接管理 提供抽象数据访问能力
基础设施层 数据库、中间件、文件、外部系统实现 实现业务层定义的技术接口
集成层 协议适配、消息转换和外部协同 隐藏外部系统差异

三、业务逻辑层组件的类型

1. 应用服务

应用服务面向业务用例,负责组织一次完整的应用操作。它通常处理输入命令、权限上下文、事务、领域对象调用、外部服务协调和结果转换。例如创建订单、审批报销、登记入院和生成发票都可以是应用服务用例。

应用服务应保持相对薄,重点是编排而不是承载所有规则。它可以判断流程顺序和事务边界,但核心业务不变量应由领域对象或领域服务保证。应用服务不应直接暴露数据库实体给客户端,而应返回命令结果、查询模型或领域事实。

2. 领域服务

领域服务承载不适合放在单个实体中的业务规则,例如多个聚合之间的定价、额度计算、路线匹配和资源分配。领域服务应使用领域语言命名,不应依赖具体 Web 框架、数据库或消息中间件。它可以由应用服务调用,也可以协作领域对象完成业务决策。

3. 领域实体与聚合

具有身份和生命周期的业务对象通常建模为实体;一组必须保持一致性边界的对象可以组成聚合。聚合根负责对外暴露修改入口,保护内部不变量。业务组件设计时要明确哪些状态变化必须在一个事务中完成,哪些可以通过事件和补偿最终一致。

4. 查询服务

查询服务针对读操作组织数据,可使用专用查询模型、缓存、搜索引擎或数据仓库。查询服务不应为了复用写模型而强行加载完整领域对象。读写分离可以提升性能和清晰度,但查询结果仍要满足权限、租户和数据时效要求。

5. 策略与规则组件

当定价、审批、路由、资格判断等规则变化频繁或存在多个可替换算法时,可以抽取策略组件或规则服务。规则组件需要有版本、适用范围、输入输出和解释能力。不能把所有规则都做成动态配置,否则规则追踪、测试和审计会困难。

6. 外部系统适配器

业务层依赖支付、物流、短信、财务和身份等业务接口,适配器负责将外部协议转换为内部模型。适配器还要处理超时、重试、错误映射、幂等和版本兼容,但不应改变核心业务决策。这样可以替换供应商或接入新系统而不污染业务组件。

四、组件划分方法

1. 按业务能力划分

组件应围绕稳定的业务能力命名,例如客户管理、订单履约、库存分配、费用报销和风险审核,而不是按技术操作命名为“工具服务”“通用处理器”。业务能力划分有利于复用和责任归属,也有利于未来服务化或团队分工。

2. 按变化原因划分

经常变化的定价规则和相对稳定的订单生命周期不宜混在一个巨大组件中;外部支付协议和内部支付业务语义也应隔离。将变化原因不同的内容分开,可以缩小修改影响范围。

3. 按一致性边界划分

必须原子更新的数据应尽量位于同一事务和聚合边界内。跨边界操作应采用事件、补偿、对账或流程状态,而不是让一个组件直接操纵多个模块的内部数据。组件划分不应只看功能名称,还要看业务不变量和一致性要求。

4. 按团队和部署边界划分

组件的所有者、开发团队和发布节奏也是重要因素。高频变化且由独立团队负责的能力可以形成相对独立的组件;强耦合、必须同时发布的能力不宜过度拆分。逻辑组件和物理服务不是一回事,应先稳定业务边界,再决定是否独立部署。

5. 控制组件大小

组件太大,职责不清,测试和发布困难;组件太小,会导致调用链过长和接口协调成本上升。可用内聚性、耦合度、变化频率、调用关系、数据边界和故障隔离等指标持续评估。不要把“类的数量”当作组件设计质量的唯一指标。

五、接口设计

1. 面向业务意图

接口名称和参数应表达业务意图,例如“提交订单”“确认收货”“申请退款”,而不是暴露“更新订单状态”或“修改某字段”。意图接口可以集中校验和审计,避免调用方任意改变内部状态。

2. 命令与查询分离

命令会改变状态,需要权限、幂等和事务;查询通常不改变状态,可以使用缓存和专用模型。分离命令与查询有利于控制副作用和优化性能。查询接口不要为了“复用”返回过度庞大的领域对象。

3. 输入输出模型

接口应使用命令对象、查询参数和结果模型,避免直接暴露数据库实体。模型要描述必填、默认、敏感字段、版本、错误码和兼容策略。输出应包含业务状态、下一步动作或可追踪编号,帮助表现层处理异步和异常。

4. 错误语义

业务拒绝、权限不足、数据冲突、外部超时和系统异常应区分表达。错误不能只返回“处理失败”,也不应把数据库异常堆栈泄露给客户端。错误码应稳定,错误信息要能被用户理解、被程序处理并被监控统计。

5. 幂等与并发控制

创建、提交、支付和审批接口要定义幂等键;修改接口可采用版本号、乐观锁或条件更新防止覆盖并发修改。接口契约要说明重复请求的结果和冲突处理,而不是依赖调用方自行猜测。

六、事务与一致性

1. 事务边界由用例决定

应用服务通常是事务边界的候选位置,但不是每个方法都必须开启一个大事务。事务范围应保护关键业务不变量,避免包含长时间网络调用和不必要的外部操作。外部调用可以采用预留、确认、异步事件和补偿等方案。

2. 本地事务

单个数据库或单个聚合内的强一致更新适合使用本地事务。事务应短小、可回滚、可重试,并避免锁住不必要的数据。数据库事务不能解决跨系统的业务一致性问题。

3. 最终一致与补偿

订单创建后通知库存、物流和积分,往往可以通过事件实现最终一致。每个消费者要幂等处理并反馈状态;失败消息进入重试和死信队列;对于无法自动恢复的情况,提供人工补偿和对账。最终一致不是忽略一致性,而是明确一致性目标、时间窗口和恢复方式。

4. 长事务与流程状态

跨小时或跨天的审批和理赔不应保持数据库长事务,应把过程拆成状态转移,并持久化流程状态。每一步完成后发布事实,下一步依据事实继续,异常通过补偿和人工处理闭环。

七、依赖控制与测试

1. 依赖倒置

业务层定义需要的抽象,例如支付网关、消息发布器、客户仓储和时钟服务,基础设施层实现这些抽象。这样业务逻辑不依赖具体数据库和第三方SDK,测试时可以使用替身。

2. 避免循环依赖

组件之间的调用应形成可理解的方向。若订单组件直接依赖客户内部数据库、客户组件又依赖订单服务,就会形成隐式循环。可以通过查询接口、领域事件、共享标识和明确的职责边界打破循环。

3. 测试分层

领域规则用单元测试验证,应用服务用组件测试验证用例、事务和权限,适配器用契约和集成测试验证外部协议,关键链路用端到端测试验证用户价值。测试数据应覆盖成功、重复、并发、超时、权限不足和补偿场景。

4. 可观测性

业务组件应记录用例、业务单号、请求ID、租户、状态转换、耗时和结果。监控不仅看异常数量,还要看业务失败率、处理延迟、重试和补偿数量。敏感信息要脱敏,日志不能破坏隐私和合规要求。

八、案例分析:电商售后业务组件设计

某电商系统的售后逻辑分散在订单控制器、客服页面和数据库存储过程中。退货、退款、换货和维修规则互相调用,支付接口和物流接口也直接写在业务方法中。新增平台规则时,需要修改多个模块,且不同渠道的售后状态不一致。

1. 组件划分

建立售后申请应用服务、售后领域服务、订单查询服务、退款适配器、物流逆向适配器、规则策略组件和售后通知组件。售后申请聚合保护申请状态、原因、凭证和处理结果;订单服务只提供查询和必要的业务确认,不允许售后组件直接修改订单表。

2. 业务流程

用户提交售后申请后,应用服务校验身份和订单归属,领域服务根据商品、时间和售后类型判断资格;审核通过后发布售后审核通过事件;退款适配器调用支付系统,物流适配器创建逆向运单;各环节反馈结果,售后聚合通过状态机进入退款中、待收货、已完成或人工处理。

3. 一致性处理

售后申请创建在本地事务中完成;退款和物流调用采用异步处理并使用业务幂等键;支付超时不立即判定失败,而是查询退款状态;多次回调按退款单号和状态版本去重;对账任务核对售后状态、支付状态和物流状态。

4. 质量与治理

服务契约定义错误码、超时、SLA和版本;规则策略按平台和商品类型版本化;表现层只展示服务端返回的允许动作;所有状态变更记录审计。通过售后处理时长、重复退款数、人工介入率、接口失败率和跨渠道状态差异评价效果。

九、综合知识练习与详细答案

练习一:单项选择

业务逻辑层组件划分最重要的依据之一是( )。

A. 类文件数量 B. 数据库表数量 C. 业务职责与一致性边界 D. 开发人员个人偏好

答案:C。组件应围绕业务能力、变化原因和一致性边界划分,不能简单按文件、表或个人习惯划分。

练习二:多项选择

应用服务通常可以承担( )。

A. 用例编排 B. 事务边界管理 C. 业务规则最终保证 D. 权限上下文处理 E. 页面布局渲染

答案:A、B、D。业务规则可以由应用服务参与校验,但核心不变量应由领域对象或领域服务保证;页面渲染属于表现层。

练习三:案例题

某业务组件的方法同时完成页面跳转、数据库更新、发送短信和调用支付接口,并在一个数据库事务中等待所有外部调用完成。请分析问题并提出改进方案。

参考答案:该组件混合了表现层、业务层、数据访问和基础设施职责,内聚性差;长事务包含网络调用,容易锁资源、超时和回滚失败;短信和支付属于外部副作用,数据库回滚不能撤销;缺少幂等、状态和补偿,失败后难以恢复。改进时应让表现层负责导航,应用服务负责用例编排和本地事务,领域对象负责核心不变量,数据访问和外部调用通过抽象接口隔离;本地事务只保存业务状态和待处理事件,外部支付、短信采用可靠消息或任务状态异步处理;为每个外部操作定义业务幂等键、超时、重试、熔断和补偿;用状态机表达处理进度,并记录请求ID、业务单号和审计;通过组件、契约和端到端测试验证成功、重复、超时和人工补偿场景。

十、案例题答题模板

遇到业务组件设计类题目,可以按以下结构作答:

  1. 先分析现状职责混杂、重复规则、强耦合和事务过大的问题。
  2. 按业务能力、变化原因和一致性边界划分应用服务、领域服务、实体、查询服务和适配器。
  3. 设计面向业务意图的接口,区分命令与查询,定义错误、幂等、版本和权限。
  4. 明确本地事务边界,对跨系统操作采用事件、状态机、补偿和对账。
  5. 通过依赖倒置隔离数据库、中间件和外部厂商,避免循环依赖。
  6. 建立单元、组件、契约、集成和端到端测试,以及业务可观测性。
  7. 用交付周期、规则重复率、故障影响范围和业务处理指标验证架构效果。

十一、论文写作素材

开头素材

“原有业务系统中,页面控制器直接访问数据库并调用外部支付和通知接口,业务规则分散在多个入口,导致多终端行为不一致、事务范围过大和改动影响难以控制。为提升系统的可维护性和扩展性,我参与重新设计业务逻辑层组件,按照业务能力、一致性边界和变化原因划分应用服务、领域服务、查询服务和外部适配器。”

正文素材

可以重点写四点:第一,如何区分应用编排、领域规则、查询和基础设施适配;第二,如何以业务意图设计接口并处理幂等、并发、错误和版本;第三,如何通过本地事务、事件、状态机和补偿处理跨系统一致性;第四,如何通过依赖倒置、测试分层、日志链路和指标治理保证长期运行。要写出实际权衡,例如核心写操作保持强一致,通知和外部支付采用异步最终一致,查询使用专用模型提升性能。

结尾素材

“实践表明,业务逻辑层组件设计的关键不是类和服务数量,而是是否准确表达业务责任并控制变化传播。通过稳定的组件边界、清晰的服务契约、合理的事务策略和可观测的异常补偿机制,系统才能在复用业务能力的同时保持可测试、可演进和可治理。”

十二、今日复盘

本节的核心结论是:应用服务组织用例,领域对象和领域服务保护业务规则,查询服务优化读取,适配器隔离外部技术;组件设计必须同时考虑职责、变化、事务和责任归属。

请回答:

  1. 应用服务与领域服务的区别是什么?
  2. 为什么业务组件不应直接暴露数据库实体?
  3. 如何根据一致性边界划分组件和事务?
  4. 跨系统操作为什么需要状态机、事件或补偿?
  5. 如何防止业务逻辑层变成“万能服务”?
  6. 如何用测试和运行指标证明组件设计有效?

请以订单、售后、采购或审批系统为例,画出“表现层—应用服务—领域组件—数据访问/外部适配器”的关系,并标出事务边界、幂等键、事件、补偿、权限和审计位置。完成这一练习,就掌握了 13.3.1 的核心考查能力。