第2章 组织领域逻辑

按规则复杂度、数据结构和用例变化选择事务脚本、领域模型、表模块与服务层。

学习目标

  • 能区分事务脚本、领域模型、表模块和服务层各自承载的责任
  • 能用 TypeScript 把订单折扣规则放进可测试的领域对象,并由服务层编排用例
  • 能根据规则复杂度、复用范围和数据结构变化,判断何时保留过程式逻辑以及何时迁移

为什么第2章组织领域逻辑值得单独学习

第2章组织领域逻辑的核心不是选择一个流行框架,而是决定业务规则落在哪里、由谁维护以及如何在多个用例之间保持一致。订单折扣与授信一开始可能只有几条线性判断,但当规则开始共享、组合和频繁变化时,把所有逻辑塞进控制器或服务层过程会让每次改动都扩大回归范围。

不是低级方案;它的边界是规则是否仍能由单个用例清楚拥有。本文以 2024 年中文版公开目录限定第2章的范围,并依据 Martin Fowler 的作者图书页和模式目录独立重写;目录只决定要讲什么,案例、实验、判断题和答案均为本课程原创。

先建立直觉:问题、机制与代价

适合规则复杂、对象关系丰富且变化频繁的领域,但会增加对象设计、持久化映射和团队理解成本。

则更贴近数据操作;

负责编排而不是替代领域模型。

对第2章组织领域逻辑,优先比较的替代路线是:简单且局部的用例使用事务脚本,行为与数据紧密对应时考虑表模块,跨用例共享且规则复杂时引入领域模型,再由服务层协调事务和端口。若实验只能显示一次调用成功,不能指出规则所有者、共享范围和变化成本,就不能据此选择组织方式。

目录单元到教学证据

第2章 组织领域逻辑

第2章组织领域逻辑要求把“业务规则放在哪里”变成可观察的架构决定:先标出规则所有者、调用者、状态不变量和事务边界,再用订单折扣与授信的变更测试验证决定。只有新增规则时能看见影响范围,组织策略才不是类名或目录结构的装饰。

2.1 抉择

2.1 抉择关注的是复杂度与变化,而不是哪一种模式听起来更先进。先统计规则分支、共享用例数、跨对象不变量和测试隔离成本;当逻辑仍然线性且局部时保留事务脚本,当同一规则被多个用例重复实现时再考虑领域模型或表模块。

2.2 服务层

2.2 服务层把请求转换为一次用例:加载对象、调用领域行为、协调外部端口并提交事务。服务层不应成为所有规则的垃圾桶;如果它开始计算折扣、修改授信不变量并复制到多个入口,就应该把稳定行为移回领域模型或明确的策略对象。

专属代码案例:订单折扣与授信

把订单折扣与授信切成“用例入口 → 领域行为 → 授信端口 → 事务提交 → 可观察结果”五个观察点。第2章组织领域逻辑的设计草案必须写出谁拥有状态、谁作出业务决定、失败怎样传播,以及复杂度、事务脚本、领域模型、表模块和服务层中哪个指标最先提示当前方案不再适用。

设计记录采用五个可换行字段:单元键为 poeaa24-chapter-02-organizing-domain-logic;模式族为 domain;裁决是“规则局部则保留事务脚本,规则共享且复杂则由领域模型拥有,服务层只编排”;观测项包括复杂度、事务脚本、领域模型、表模块、服务层;拒绝条件是“同一折扣规则在多个服务入口复制且没有单一不变量所有者”。

先预测:如果新增“会员等级折扣”并要求授信额度也参与判断,哪一段代码会变化?先记录事务脚本、领域模型和服务层各自的修改范围,再阅读实现;验证时应能说明领域对象保护了什么不变量,以及服务层为何没有吞掉业务规则。

type OrderLine = { sku: string; unitCents: number; quantity: number };
 
type CreditDecision =
  | { approved: true; remainingCents: number }
  | { approved: false; reason: "limit" | "blocked" };
 
interface CreditPort {
  authorize(customerId: string, amountCents: number): CreditDecision;
}
 
class Order {
  constructor(
    readonly id: string,
    readonly customerId: string,
    private readonly lines: OrderLine[],
  ) {}
 
  totalCents(): number {
    return this.lines.reduce(
      (sum, line) => sum + line.unitCents * line.quantity,
      0,
    );
  }
 
  totalAfterDiscountCents(discountRate: number): number {
    return Math.round(this.totalCents() * (1 - discountRate));
  }
}
 
function placeOrder(
  order: Order,
  discountRate: number,
  credit: CreditPort,
): "accepted" | "rejected" {
  const amount = order.totalAfterDiscountCents(discountRate);
  const decision = credit.authorize(order.customerId, amount);
  return decision.approved ? "accepted" : "rejected";
}

这段代码让订单对象拥有金额计算,让服务入口只协调折扣输入与授信端口。真实系统还应把折扣资格、不变量和持久化提交放进可测试的领域边界;如果 placeOrder 开始复制会员、地区和授信规则,就应把这些行为提炼为领域策略,而不是继续扩张服务层。

三种策略解剖

组织领域逻辑没有固定的升级顺序。事务脚本适合简单用例,表模块适合行为与记录集紧密对应的场景,领域模型适合跨对象不变量与共享规则;服务层横跨这些方案,负责协调用例而不是替它们拥有全部业务知识。下面的图把策略的适用条件与迁移代价放在同一张证据图中。

组织领域逻辑的三种策略事务脚本Transaction Script核心思想一个用例 = 一个过程代码结构线性步骤序列适用场景规则少、分支少代价规则增长后重复爆炸典型代码if/else 流程式处理领域模型Domain Model核心思想业务规则 = 对象协作代码结构对象网络 + 多态适用场景规则复杂、频繁变化代价学习曲线 + 映射开销典型代码Order.calculateTotal()表模块Table Module核心思想一个类管一张表的所有行代码结构面向记录集的方法适用场景结构化查询为主代价复杂规则难以表达典型代码OrderTable.findByStatus()迁移方向:规则增长 → 从事务脚本向领域模型演进简单复杂事务脚本表模块领域模型没有最好的策略,只有最匹配当前复杂度的策略——关键是知道何时该迁移
事务脚本适合简单用例,领域模型适合复杂规则,表模块适合结构化查询。 随着业务规则增长,代码会自然从事务脚本向领域模型迁移。

当规则分支、共享用例和跨对象不变量同时增长时,应比较迁移成本,而不是机械地把所有代码改成领域对象;小而稳定的规则留在事务脚本中,反而更容易测试和维护。

组织策略决策图

先看规则是否局部,再看是否存在跨对象不变量与多用例复用,最后检查服务层是否仍然只是编排。若没有真实复杂度证据,不要为了“未来灵活性”引入完整领域模型;若已经出现复制规则和不变量分裂,则应停止继续堆叠过程代码。

领域逻辑:用复杂度证据选择组织方式规则局部线性事务脚本一个用例一个过程测试局部规则记录集行为集中表模块查询与写回紧密对应复杂跨对象规则受限共享不变量增长领域模型对象协作与策略服务层负责编排拒绝条件:规则复制或不变量没有唯一所有者服务层协调用例,不应成为所有业务规则的垃圾桶
从事务脚本到表模块再到领域模型,选择由规则复用、跨对象不变量和变化成本推动。

选择与拒绝矩阵

评审问题选择组织策略的证据应拒绝当前方案的信号
责任规则所有者、服务层编排者和持久化边界清晰控制器、服务层和表模块都能改写同一不变量
变化规则变化只影响对应策略和测试新增一个折扣条件要复制多个用例过程
复用共享规则只有一个可调用的领域入口每个入口各自计算金额、授信与资格
替代已比较事务脚本、表模块和领域模型的真实成本因团队偏好或框架示例直接选择复杂方案

常见误区

可验证练习

练习

本组练习覆盖 2.1 抉择和 2.2 服务层,并要求把复杂度、事务脚本、领域模型、表模块和服务层映射到订单折扣与授信。

问题 1:选择起点。 一个订单用例只有两条折扣规则,没有跨对象不变量,也没有第二个调用入口,应从哪种组织方式开始?

问题 2:识别服务层边界。 多个入口都需要相同的会员折扣和授信规则,服务层开始出现重复计算,应该怎么改?

问题 3:比较表模块。 订单查询主要是按状态筛选记录,但新增规则会跨订单、客户和授信额度,继续让 OrderTable 承担全部行为是否合适?

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

事务脚本

把一个业务用例按步骤写成过程,由过程读取数据、判断规则并完成写回的组织方式。

领域模型

把跨用例共享的业务规则和不变量放进相互协作的领域对象的组织方式。

表模块

代表单张业务表或记录集,并为该表的查询与操作提供方法的组织方式。

服务层

负责协调用例步骤、事务边界和外部端口,但不替代领域对象拥有核心业务规则的应用入口。

本章小结

掌握第2章组织领域逻辑的标志不是记住某种模式名称,而是能在订单折扣与授信中解释“用例入口 → 领域行为 → 授信端口 → 事务提交 → 可观察结果”的责任链。学习者应利用复杂度、事务脚本、领域模型、表模块和服务层作出可证伪的选择,并在规则复制或不变量所有权分裂时明确拒绝当前实现。

前后导航

来源与改写范围

资料与写作方式声明

本章以Martin Fowler《企业应用架构模式》与公开模式目录权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…