应用服务(Application Service)
本文档说明
io.pragmatic.ddd.application包提供的应用服务编排能力:命令执行器、工作单元、应用服务基类、实体工厂/更新器/属性解析器,以及 Outbox 事务性发件箱。前置阅读:领域建模 · 业务规则引擎 · 领域事件。
1. 概述
1.1 核心定位
应用服务层负责编排"领域逻辑 → 规则校验 → 持久化 → 事件分发"的标准流程,并对外部调用屏蔽模板细节。框架提供两类编排入口与一个便捷基类:
- 单聚合根命令:一次操作只涉及一个聚合根,使用
CommandExecutor。 - 跨聚合根工作单元:一次操作涉及多个聚合根、需统一提交,使用
UnitOfWork。 - 应用服务基类:
AbstractApplicationService内聚二者,提供语义化方法。
此外,Outbox 模式(application.outbox)提供事务性发件箱,保证事件与聚合根同事务原子落库。
两者均提供零副作用的试跑(Dry-run)入口,用于预览校验结论而不落库、不发事件。
1.2 概念层级与依赖关系
ICommandExecutor ──AbstractCommandExecutor── CommandExecutor(默认)
IUnitOfWork ──AbstractUnitOfWork──── UnitOfWork(默认)/ OutboxUnitOfWork
AbstractApplicationService 聚合 ICommandExecutor + IUnitOfWork 工厂
EntityFactory<T,C> 创建新聚合根
EntityUpdater<T,C> 修改已有聚合根(apply)
IEntityPropertyResolver<C,E,R> / EntityPropertyResolvers 属性解析适配
application.outbox
├─ OutboxCommandExecutor / OutboxUnitOfWork 事务性发件箱执行器
├─ EagerOutboxPublisher 提交后主动推送
├─ OutboxRelay + OutboxRelayConfig 兜底轮询补偿
└─ OutboxStatus / OutboxMessage / OutboxEntry 状态机与载体
application.compensation(分布式事务补偿,叠加于 Outbox / 默认执行器之上)
├─ CompensableCommandExecutor 装饰任意 ICommandExecutor,为声明外部需求的命令叠加逆序补偿
├─ CompensationActionRegistry 按需求类型(Class)索引可补偿动作
├─ LocalCompensationScope / CompensationScopes 补偿状态机(内存 / WAL 两态)
├─ CompensationTemplate 固化「成功提交 / 异常逆序补偿并重抛」
├─ CompensationOptions / CompensationPolicy 模式开关与重试策略
├─ ICompensableAction 正向执行 / 回写聚合 / 逆向补偿契约
└─ relay/CompensationRelay + spi/* 兜底中继(WAL 回收与死信)与持久化日志 / 崩溃执行器 SPI| 类型 | 包路径 | 用途 |
|---|---|---|
ICommandExecutor / AbstractCommandExecutor / CommandExecutor | io.pragmatic.ddd.application | 单聚合根命令(默认:同事务 save,提交后 publish) |
IUnitOfWork / AbstractUnitOfWork / UnitOfWork | io.pragmatic.ddd.application | 跨聚合根工作单元(默认:统一 save 后 publishList) |
AbstractApplicationService | io.pragmatic.ddd.application | 应用服务便捷基类 |
DryRunResult | io.pragmatic.ddd.application | 试跑(Dry-run)结构化结果(record) |
EntityFactory / EntityUpdater | io.pragmatic.ddd.application | 实体创建 / 修改契约 |
IEntityPropertyResolver / EntityPropertyResolvers | io.pragmatic.ddd.application | 实体属性解析契约与适配器工厂 |
OutboxCommandExecutor / OutboxUnitOfWork | io.pragmatic.ddd.application.outbox | 事务性发件箱执行器 |
EagerOutboxPublisher / OutboxRelay / OutboxRelayConfig | io.pragmatic.ddd.application.outbox | 主动推送 / 兜底轮询 / 运行配置 |
OutboxStatus / OutboxMessage / OutboxEntry | io.pragmatic.ddd.application.outbox | 发件箱状态机与载体 |
CompensableCommandExecutor | io.pragmatic.ddd.application.compensation | 可补偿命令执行器(装饰器,为声明外部需求的命令叠加逆序补偿) |
CompensationActionRegistry | io.pragmatic.ddd.application.compensation | 可补偿动作注册表(按需求类型建索引,注册期查重) |
CompensationOptions / CompensationPolicy | io.pragmatic.ddd.application.compensation | 补偿模式(inMemory / durableMode)与单动作重试策略 |
ICompensableAction | io.pragmatic.ddd.application.compensation | 正向执行 / 回写聚合 / 逆向补偿动作契约 |
IExternalRequirement | io.pragmatic.ddd.base | 外部需求契约:聚合声明、触发补偿的运行时凭证(L3 路由键 = code()) |
ICompensationLog | io.pragmatic.ddd.application.compensation.spi | 持久化补偿日志 SPI(WAL,REQUIRES_NEW 短事务) |
ICompensationHandler | io.pragmatic.ddd.application.compensation.spi | 崩溃兜底补偿执行器(按 actionName() 编码路由) |
CompensationRelay | io.pragmatic.ddd.application.compensation.relay | 补偿兜底中继(回收超时认领 / 重试 / 死信) |
2. 核心概念详解
2.1 单聚合根命令:ICommandExecutor / CommandExecutor
AbstractCommandExecutor 固定五步模板,CommandExecutor(默认)在 persistAndDispatch 钩子中同一事务内落库、事务提交后再发布事件:
1. 执行领域逻辑 domainLogic.accept(aggregateRoot)
2. 规则校验 aggregateRoot.satisfiesRule(rule) → 未通过则 throwBrokenRuleException
3. 持久化(事务内) repository.save(aggregateRoot)
4. 发布事件(事务外)事件快照 forEach(eventManager::publish) ← 事务已提交
5. 清空状态 aggregateRoot.clearWorkUnitState() ← 置于 finally⚠️ 单个聚合根在数据库中往往映射为多张表(主表 + 从表),一次
repository.save会发出多条 SQL。因此CommandExecutor把落库整体收敛到一个数据库事务(TransactionOperations,默认REQUIRED):任一条 SQL 失败则整体回滚,不会残留"主表已写、从表未写"的撕裂态。事件发布必须晚于事务提交——提交前发出而事务随后回滚,会让下游收到数据库中并不存在的业务事实。
⚠️
CommandExecutor走的是"提交后直接发 MQ",不保证事件不丢:publish在事务提交后失败时,数据已落库但事件永久丢失。需闭合该缺口请用 Outbox 链路(见 §3)。
IEventManager eventManager = new ThreadPoolEventManager(LocalEventManagerConfig.defaultConfig());
eventManager.start();
// 事务抽象由基础设施模块提供实现(Spring 下用 TransactionTemplate 适配)
TransactionOperations txOps = new SpringTransactionOperations(platformTransactionManager);
CommandExecutor executor = new CommandExecutor(eventManager, txOps);
OrderRepository repository = new OrderRepository();
OrderRule rule = new OrderRule();
Order order = new Order(1L, "张三", 100);
Order result = executor.execute(order, rule, repository, Order::cancel);事务为必填参数。明确不需要事务的场景须显式传入
NoOpTransactionOperations(该实现置于test,生产不提供),避免"以为有事务"的假象。仓储侧需使用由 Spring 托管的SqlSessionTemplate(或等价物)才能自动加入当前事务,不可使用裸SqlSessionFactory#openSession()。
试跑 Dry-run
tryExecute 执行与 execute 完全相同的领域逻辑与规则校验,但跳过持久化与事件分发,以 DryRunResult 结构化返回;rule 为 null 时视为无规则约束;仅规则类异常被转译为未通过,其余异常照常上抛。
DryRunResult result = executor.tryExecute(order, rule, repository, Order::cancel);
if (result.passed()) {
// 校验通过
} else {
List<BrokenRule> brokenRules = result.brokenRules(); // 规则违反明细
}⚠️ 重要约束:试跑后的聚合根状态已变更(领域逻辑已执行),且
tryExecute内部已清空暂存事件;该实例不可再用于真实execute,应使用专用实例试跑。
2.2 跨聚合根工作单元:IUnitOfWork / UnitOfWork
AbstractUnitOfWork 固定多聚合根统一提交流程,划分为三阶段:领域逻辑与规则校验在事务外完成,持久化落在独立的数据库事务内,事件发布在事务提交之后:
阶段一(事务外)validateAndCollect
1. 逐条执行领域逻辑 domainLogic.accept
2. 逐条规则校验 未通过则收集明细,全部违反聚合为 BrokenRuleAggregateException 抛出
3. 汇总全部事件 collected.addAll(getDomainEvents)
—— 任一违反即终止:事务根本不会开启,一个库都不碰 ——
阶段二(事务内)persistAndCollect
4. 逐条持久化 repository.save(纯数据库写,事务由基类统一开启)
5. 落 outbox / 清空 OutboxUnitOfWork 整批落 PENDING,随后 clearWorkUnitState
阶段三(事务外)dispatchEvents
6. 统一发布 eventManager.publishList / EagerOutboxPublisher.publishAfterCommit与 CommandExecutor 的区别:先全部校验,再统一落库,最后统一发布事件,适合需要事务一致性的多聚合根操作。事务是工作单元的固有语义:AbstractUnitOfWork 统一持有事务抽象,阶段二的所有 save(含同一聚合的多次操作)必然落在同一个数据库事务内,任一条目失败整体回滚。
try (IUnitOfWork uow = new UnitOfWork(eventManager, txOps)) {
uow.register(order, orderRule, orderRepository, Order::cancel)
.register(inventory, inventoryRule, inventoryRepository, Inventory::deduct)
.commit(); // 事务外校验 → 事务内统一落库 → 事务外发布事件
}⚠️ 重要约束:校验与持久化分离带来并发窗口。 阶段一校验通过到阶段二落库之间,聚合可能被其它请求修改。因此参与工作单元的聚合必须具备乐观锁版本,
doUpdate须校验affected rows,为 0 时抛乐观锁冲突异常使事务回滚。
⚠️ 事务是必填项:
UnitOfWork构造器必须显式注入事务抽象TransactionOperations。无事务场景(如单测、纯本地事件)须显式传入NoOpTransactionOperations,不能依赖默认。OutboxUnitOfWork与UnitOfWork均共享基类同一事务边界。
UnitOfWork 实现 AutoCloseable:未提交时 close() 自动清空各条目事件,防止内存泄漏;commit() 与 tryCommit() 均幂等保护,重复调用抛 IllegalStateException。
阶段一或阶段二失败时(规则违反、落库异常),committed 保持 false,close() 仍会清理暂存事件;仅阶段三发布失败时 committed 已为 true,不清理,由 OutboxRelay 兜底补偿。
试跑 tryCommit
tryCommit 复用阶段一的领域逻辑与规则校验,跳过持久化与事件分发;任一条目未通过时收集明细、不中断其余条目;试跑会消费工作单元,之后不可再 commit/tryCommit。
DryRunResult result = unitOfWork.tryCommit(); // 零副作用,返回聚合全部条目校验结论2.3 应用服务基类:AbstractApplicationService
应用服务基类,内聚 ICommandExecutor 与 IUnitOfWork 工厂;继承者必须显式注入命令执行器与工作单元工厂,以明确声明自身的一致性语义。子类以语义方法暴露能力,无需直接持有编排器。
public class OrderApplicationService extends AbstractApplicationService {
private final OrderRepository orderRepository;
private final OrderRule orderRule;
public OrderApplicationService(IEventManager eventManager,
ICommandExecutor commandExecutor,
Supplier<IUnitOfWork> unitOfWorkFactory,
OrderRepository orderRepository,
OrderRule orderRule) {
super(eventManager, commandExecutor, unitOfWorkFactory); // 显式声明执行器与工厂语义
this.orderRepository = orderRepository;
this.orderRule = orderRule;
}
public void cancelOrder(Long orderId) {
Order order = orderRepository.findById(orderId);
execute(order, orderRule, orderRepository, Order::cancel);
}
public DryRunResult tryCancelOrder(Long orderId) {
Order order = orderRepository.findById(orderId);
return tryExecute(order, orderRule, orderRepository, Order::cancel);
}
public void cancelWithInventory(Order order, Inventory inventory, ...) {
try (IUnitOfWork uow = beginUnitOfWork()) {
uow.register(order, orderRule, orderRepository, Order::cancel)
.register(inventory, inventoryRule, inventoryRepository, Inventory::deduct)
.commit();
}
}
}唯一受保护构造器(必须显式注入执行器与工厂):
| 构造器 | 用途 |
|---|---|
AbstractApplicationService(IEventManager, ICommandExecutor, Supplier<IUnitOfWork>) | 全自定义:注入命令执行器 + IUnitOfWork 工厂 |
执行器与工作单元必须语义一致:默认场景用
CommandExecutor+UnitOfWork,outbox 场景用OutboxCommandExecutor+OutboxUnitOfWork,禁止混用,否则同一服务内出现两套一致性语义。装配通常交由组合根(如@Configuration)以 Bean 提供成品。
重要约束:
AbstractApplicationService仅为应用服务基类,不强制继承;也可直接组合ICommandExecutor/IUnitOfWork使用。
2.4 实体工厂、更新器与属性解析器
框架提供创建与修改聚合根的契约分离,以及属性解析的适配器工厂。
EntityFactory<T, C>:创建新实体
public interface EntityFactory<T extends AggregateRoot<?>, C> {
T create(C command); // 从 Command DTO 构建新聚合根,遵循"先算后赋"
}EntityUpdater<T, C>:修改已有实体
public interface EntityUpdater<T extends AggregateRoot<?>, C> {
void apply(T aggregateRoot, C command); // 从 Command 计算变更并调用实体业务方法完成修改
}IEntityPropertyResolver<C, E, R> / EntityPropertyResolvers:属性解析适配
IEntityPropertyResolver 在具体场景下从 Command DTO 与实体现状解析出实体属性值;EntityPropertyResolvers 适配器工厂将同一个实体属性计算领域服务(IEntityPropertyCalculator)适配到不同场景的 Command DTO,一处定义多处复用。
// 取数仅依赖 Command
IEntityPropertyResolver<CreateCmd, Order, BigDecimal> r1 =
EntityPropertyResolvers.of(calculator, CreateCmd::getAmount);
// 取数需同时依赖 Command 与实体现状
IEntityPropertyResolver<UpdateCmd, Order, BigDecimal> r2 =
EntityPropertyResolvers.of(calculator, (cmd, entity) -> cmd.extract(entity));⚠️ 重要约束:
EntityUpdater的方法是apply(非update);IEntityPropertyResolver的方法是resolve(C, E),创建场景下可调用resolve(C)(entity 置null)。
2.5 应用服务分层标记
public interface ICommandApplicationService { } // 命令服务(写操作)
public interface IQueryApplicationService { } // 查询服务(读操作)读写分离标记,配合仓储的 IRepository(写)与 IAggregateProjection(读)实现读写分离:
public class OrderCommandService extends AbstractApplicationService
implements ICommandApplicationService { /* 写操作 */ }
public class OrderQueryService implements IQueryApplicationService { /* 读操作 */ }2.6 可补偿命令执行器(分布式事务补偿)
CompensableCommandExecutor 是 ICommandExecutor 的装饰器:包装任意真实执行器(默认 / Outbox),为其中声明了外部需求的命令叠加「正向执行外部动作 → 失败逆序补偿」的能力。它把「跨本地事务边界的分布式补偿」收敛到应用层编排内,使一次用例既能落库发事件、又能对外部副作用做回滚,且不引入第二套事务语义。
CompensableCommandExecutor ──装饰──→ ICommandExecutor(默认 / Outbox)
│ 段一 领域逻辑(纯内存,收集外部需求)
│ 段二 逐项需求解析 + 补偿范围内正向执行 + 回写聚合
│ 段三 规则校验 → 持久化 → 事件分发(由 delegate 完成)
└─ 整体由 CompensationTemplate 包裹:成功提交;任一环节失败按正向逆序补偿已执行动作并重抛2.6.1 三段式执行顺序(事实依据)
CompensableCommandExecutor.execute 在 CompensationScopes.begin 打开的补偿范围内运行(CompensableCommandExecutor.java:53),模板(CompensationTemplate.call)固定「成功即提交、异常即逆序补偿并重抛原异常」:
段一(delegate 内,纯内存)domainLogic.accept(aggregateRoot)
收集领域事件与外部需求(requireExternal),不触碰外部系统
段二(补偿范围内)for each requirement:registry.resolve → scope.execute(正向 + 回写聚合)
正向执行失败直接冒泡,未登记、不补偿(所见即所偿)
段三(delegate 紧随)规则校验 → repository.save → 事件发布
段三失败触发 rollback:已登记的已执行动作逆序、不中断地补偿⚠️ 事务边界由被装饰的
ICommandExecutor决定:CompensableCommandExecutor不持有事务抽象,不引入第二套一致性语义。装饰OutboxCommandExecutor即获得「外部动作可补偿 + 事件与聚合同事务原子落库」;装饰CommandExecutor则走默认。装配时二者语义须一致(见 §2.3)。⚠️ 补偿动作必须幂等:
compensate在崩溃恢复路径与正常回滚路径都会触发,外部系统须支持按业务单号幂等撤销(如按预占单号释放)。
2.6.2 契约:ICompensableAction 与 IExternalRequirement
补偿以「需求 → 动作」配对,二者均为领域 / 应用层的强类型契约:
IExternalRequirement(io.pragmatic.ddd.base):聚合在领域方法内通过requireExternal(requirement)声明「需要外部世界配合完成的一件事」,只描述要什么、不描述怎么做;code()是跨进程稳定的 L3 路由键(持久化compensation_log.handler列)。ICompensableAction<A, REQ, T>:一个「正向调用外部 + 逆向回滚外部」的配对单元,绑定所属聚合类型与需求类型——正向、回写、补偿一律从聚合读取业务事实。必须无状态(作为单例由容器管理)。
public interface ICompensableAction<A extends AggregateRoot<?>, REQ extends IExternalRequirement, T> {
Class<REQ> requirementType(); // 与 IExternalRequirement 同款 Class 路由,注册表按此建索引
Class<A> aggregateType(); // 解析期做聚合类型校验
T execute(A aggregateRoot, REQ requirement); // 正向:调用外部系统,返回供本地聚合使用的产出
void apply(A aggregateRoot, T result); // 回写:只调聚合充血方法(bindReserveNo),禁 setter
void compensate(A aggregateRoot, T result); // 逆向:调用外部回滚,必须幂等
default String payload(A, REQ) { return ""; } // durable 兜底依据;非空才能开 durable
default Optional<CompensationPolicy> policy() { return Optional.empty(); } // 单动作重试策略
}聚合在领域方法内声明需求(与 collectEvent 同构):
public void place() {
// ... 领域逻辑 ...
requireExternal(ReserveInventoryRequirement.INSTANCE); // 声明:下单需预占库存
}2.6.3 注册表与补偿范围
CompensationActionRegistry:以全部ICompensableAction构造、按requirementType()(Class)建索引,需求类型重复时构造即失败(fail-fast);解析期对动作缺失、aggregateType不匹配显式失败,不静默跳过。LocalCompensationScope/CompensationScopes:补偿状态机。登记已成功执行的命令;失败(段三或段二中途)时逆序、不中断地补偿(单个失败不影响其余);重试采用短退避(默认 3 次 / 200ms,L2 阻塞在请求线程)。CompensationOptions.durable()为真时,全程以 WAL 落ICompensationLog,rollback先原子认领避免重复补偿。CompensationTemplate:固化「成功提交 / 异常逆序补偿并重抛原异常」,关闭范围的异常一律忽略并记录,避免覆盖补偿结果与原始业务异常。
2.6.4 两种模式:内存 vs 持久化(WAL + 中继)
CompensationOptions 决定补偿日志是否落库:
| 模式 | 构造 | 持久化补偿日志 | 崩溃兜底 | 适用 |
|---|---|---|---|---|
| 内存(默认) | CompensationOptions.inMemory() | 不落库,DB 零依赖 | 无(进程崩溃则补偿中断) | 外部动作可逆且进程内即可完成回滚 |
| 持久化 | CompensationOptions.durableMode() | 落 ICompensationLog(WAL,REQUIRES_NEW 短事务) | CompensationRelay 回收超时认领 + 重试 + 死信 | 跨进程 / 需崩溃后仍可回抢补偿 |
持久化模式依赖两个 SPI 与一台中继:
ICompensationLog(SPI):WAL 状态机(PENDING → EXECUTED → COMPENSATING → COMPENSATED / CONFIRMED / FAILED),全部写方法须REQUIRES_NEW独立短事务,保证主事务回滚时 WAL 仍在。ICompensationHandler(SPI):业务为每个需「崩溃后兜底」的外部动作各提供一个,按actionName()(= 需求code)路由,compensate(payload)用持久化的业务标识还原补偿,必须幂等。CompensationRelay(原语):pollOnce()执行一次「回收超时认领 → 补偿 EXECUTED → 重试 FAILED」,超MAX_RELAY_ATTEMPTS(5)转死信交人工;本类不内建调度器,触发时机由使用方决定(@Scheduled/ xxl-job / K8s CronJob)。
// 持久化模式示例:业务提供崩溃兜底执行器,actionName 必须等于需求 code
@Component
public class ReserveInventoryCompensationHandler implements ICompensationHandler {
@Override
public String actionName() {
return "reserve-inventory"; // = ReserveInventoryRequirement.code()
}
@Override
public void compensate(String payload) {
ReservePayload p = serializer.deserialize(payload, ReservePayload.class);
inventoryDependency.releaseByOrder(p.orderId()); // 按订单标识幂等释放
}
}2.6.5 应用层落地:OrderWriteService 写法
补偿执行器由 pragmatic-ddd-spring-boot 的补偿自动装配以成品 Bean 注入(装饰 Outbox 执行器),服务内不再手动 new;仅在需要补偿的用例(如下单)使用它,其余用例继续走基类 execute:
@Service
public class OrderWriteService extends AbstractApplicationService implements ICommandApplicationService {
private final CompensableCommandExecutor compensableExecutor;
// ... 其余依赖
public OrderWriteService(IEventManager eventManager,
OutboxCommandExecutor commandExecutor,
Supplier<IUnitOfWork> unitOfWorkFactory,
CompensableCommandExecutor compensableExecutor,
/* ... */) {
super(eventManager, commandExecutor, unitOfWorkFactory);
this.compensableExecutor = compensableExecutor;
}
/** 下单:建聚合(聚合声明库存预占需求)→ 框架执行外部动作并兜底补偿 → 校验落库发事件。 */
public Order placeOrder(CreateOrderInput input) {
Order order = orderFactory.create(input);
return compensableExecutor.execute(order, orderRule, orderRepository, t -> {});
}
}2.6.6 何时用可补偿执行器(与 Outbox 区分)
CompensableCommandExecutor 与 OutboxCommandExecutor 解决不同问题,可叠加使用(补偿装饰 Outbox):
| 维度 | OutboxCommandExecutor | CompensableCommandExecutor |
|---|---|---|
| 解决的问题 | 本地事件与聚合同事务原子落库、不丢事件 | 本地事务之外的外部副作用(预占 / 扣减 / 发券)失败可回滚 |
| 关注边界 | 库内:事件 vs 聚合 | 库外:本聚合 vs 外部系统 |
| 是否引入第二套语义 | 是(Outbox 一致性) | 否(仅装饰既有执行器) |
| 典型组合 | 单独用即可 | 叠加在 OutboxCommandExecutor 上(推荐) |
⚠️ 补偿不替代 Outbox:补偿解决「外部副作用回滚」,Outbox 解决「事件可靠投递」。下单既要可靠发事件又要回滚库存预占时,用
CompensableCommandExecutor装饰OutboxCommandExecutor。⚠️ 只装饰、不得错配:被装饰的执行器与补偿执行器共享同一事务边界;不可把
CompensableCommandExecutor装饰到两套不一致的执行器上(同 §2.3 语义一致约束)。
3. 关键机制与避坑指南
3.1 Outbox 事务性发件箱
OutboxCommandExecutor / OutboxUnitOfWork 与默认执行器并存、零侵入,保证事件与聚合根同事务原子落库:
① 同事务:聚合落库 + outbox 落库(PENDING)
② 事务提交后 → EagerOutboxPublisher.publishAfterCommit 主动推送(post-commit,规避"提交前误发")
③ 若主动推送失败/崩溃(未 markSent)→ OutboxRelay 兜底轮询补偿EagerOutboxPublisher 在事务外发送原始事件,成功后 markSent(PENDING→SENT,带状态守卫、幂等);失败/崩溃不标记,保留 PENDING 交由 Relay 补偿。
OutboxCommandExecutor outboxExecutor = new OutboxCommandExecutor(
outboxStore, // IOutboxStore 实现(如 MybatisOutboxStore)
transactionOperations, // TransactionOperations(事务模板)
eventSerializer, // IEventSerializer
eagerPublisher); // EagerOutboxPublisher
outboxExecutor.execute(order, rule, repository, Order::cancel); // 用法与 CommandExecutor 完全一致3.2 Outbox 状态机与兜底轮询
OutboxStatus 四态:PENDING / PROCESSING / SENT / FAILED。
PENDING ──claim──→ PROCESSING ──publish成功──→ SENT
│
└──publish失败──→ release → PENDING
↓
attempts > max?
├── 是 → FAILED(死信)
└── 否 → 继续 PENDINGOutboxRelay 通过 claimPending(原子认领超时 PENDING 批次,WHERE status=PENDING AND created_at < now-grace LIMIT ?)补偿重发;OutboxRelayConfig.defaultConfig() 默认值:pollInterval=5min、grace=30s、batchSize=200、maxAttempts=10,亦可用 OutboxRelayConfig.bind(source) 按 outbox 前缀从配置源绑定。
OutboxRelay relay = new OutboxRelay(
outboxStore, eventManager, eventSerializer, scheduledExecutor,
OutboxRelayConfig.defaultConfig());
relay.start(); // 启动周期性轮询(scheduleAtFixedRate)⚠️ 重要约束:Outbox 执行器的事件推送发生在事务提交之后(post-commit);
markSent为独立短事务且带状态守卫,保证幂等。文档早期示例将pollInterval/batchSize/maxAttempts写为 5s/100/5 系旧值,正确默认值为 5min/200/10。
3.3 何时用 Outbox 替代默认执行器
两个执行器都已提供事务边界(单聚合多表原子落库),差别在事件可靠性:
| 场景 | 推荐 |
|---|---|
| 单体应用,事件即时发布即可,事件丢失可接受 | CommandExecutor / UnitOfWork |
| 需保证事件与聚合根同事务原子性 | OutboxCommandExecutor / OutboxUnitOfWork |
| 多实例部署,需防重复投递(claim_token 机制) | Outbox(OutboxRelay.claimPending 原子认领) |
| 事件投递可容忍少量延迟(兜底补偿) | Outbox |
4. 异常与错误处理体系
应用服务层主要复用框架既有异常,编排器自身不定义专属异常类型:
| 触发点 | 异常 | 说明 |
|---|---|---|
| 规则校验未通过 | RuleException(经 throwBrokenRuleException) | CommandExecutor/UnitOfWork 提交时中断 |
| 试跑中规则失败 | 转译为 DryRunResult.reject,不抛异常 | 仅 ICommandExecutor.tryExecute / IUnitOfWork.tryCommit |
| 工作单元重复提交/试跑 | IllegalStateException | commit() / tryCommit() 幂等保护 |
| Outbox 兜底失败超限 | OutboxRelay 标记 FAILED | 死信,不抛异常,需独立监控 |
重要约束:试跑(Dry-run)仅将规则类异常转译为未通过结论;领域逻辑中抛出的非规则异常(如空指针、基础设施异常)仍照常上抛,不会被吞掉。
5. 总结速查
| 概念 | 使用方式 | 最关键约束 |
|---|---|---|
| 单聚合根命令 | CommandExecutor.execute(t, rule, repo, logic) | 默认先 save 再逐条 publish |
| 跨聚合根工作单元 | UnitOfWork.register(...).commit()(try-with-resources) | 统一校验→落库→发布;close() 未提交自动清理 |
| 试跑 | tryExecute / tryCommit → DryRunResult | 零副作用;试跑实例不可复用;仅规则异常转译 |
| 应用服务基类 | 继承 AbstractApplicationService,显式注入 ICommandExecutor + Supplier<IUnitOfWork> | 提供 execute/tryExecute/beginUnitOfWork;非强制 |
| 实体创建/修改 | EntityFactory.create / EntityUpdater.apply | apply 非 update |
| 属性解析 | EntityPropertyResolvers.of(calc, extractor) | 一处定义多处复用 |
| 分层标记 | ICommandApplicationService / IQueryApplicationService | 配合 IRepository/IAggregateProjection 读写分离 |
| Outbox 执行器 | OutboxCommandExecutor / OutboxUnitOfWork | 同事务落库;post-commit 推送 |
| Outbox 兜底 | OutboxRelay.start() + OutboxRelayConfig | 默认 5min/30s/200/10;四态状态机 |
| 可补偿命令 | CompensableCommandExecutor(装饰器,仅用于声明外部需求的用例) | 段一逻辑 → 段二正向+回写 → 段三校验落库;异常逆序补偿 |
| 补偿动作 | ICompensableAction<A,REQ,T>(无状态单例)+ 需求 IExternalRequirement | 正向/回写/补偿三元组;payload 供 durable 兜底 |
| 补偿模式 | CompensationOptions.inMemory() / durableMode() | 内存零依赖;durable 落 WAL + CompensationRelay 兜底 |
| 补偿兜底 | ICompensationHandler(按 actionName 路由)+ CompensationRelay.pollOnce() | 崩溃后回抢;超 5 次转死信 |
6. 命名规范速查
结合框架事实约束(接口以 I 开头、执行器/工作单元语义命名、Outbox 类以 Outbox 前缀、方法名定论),约定如下:
| 元素 | 格式 | 示例 |
|---|---|---|
| 命令执行器接口/类 | ICommandExecutor / {语义}CommandExecutor | ICommandExecutor、CommandExecutor |
| 工作单元接口/类 | IUnitOfWork / {语义}UnitOfWork | IUnitOfWork、UnitOfWork、OutboxUnitOfWork |
| 应用服务基类 | Abstract{层}Service | AbstractApplicationService |
| 命令服务类 | {聚合}CommandService implements ICommandApplicationService | OrderCommandService |
| 查询服务类 | {聚合}QueryService implements IQueryApplicationService | OrderQueryService |
| 语义方法 | execute / tryExecute / beginUnitOfWork / commit / tryCommit | 基类已定义,子类按需派生 cancelOrder |
| 实体工厂 | {聚合}Factory implements EntityFactory<T,C> | OrderFactory |
| 实体更新器 | {聚合}Updater implements EntityUpdater<T,C>(方法 apply) | OrderUpdater |
| 属性解析器 | {语义}Resolver implements IEntityPropertyResolver<C,E,R> | DiscountResolver |
| Outbox 类 | Outbox{角色} | OutboxCommandExecutor、OutboxRelay、OutboxRelayConfig、EagerOutboxPublisher |
| 补偿执行器 | Compensable{角色} | CompensableCommandExecutor、CompensationActionRegistry、CompensationRelay |
| 补偿动作 | {语义}Action implements ICompensableAction<A,REQ,T> | ReserveInventoryAction |
| 崩溃兜底执行器 | {语义}CompensationHandler implements ICompensationHandler | ReserveInventoryCompensationHandler |
| 外部需求 | {语义}Requirement implements IExternalRequirement(record,code() 稳定) | ReserveInventoryRequirement |
| 试跑结果 | DryRunResult(pass() / reject(...)) | DryRunResult |
⚠️ 重要约束:
EntityUpdater的方法名为apply(非update);IEntityPropertyResolver的创建场景便捷方法为resolve(C)。命名误用将导致编译失败。Outbox 配置键统一以outbox为前缀(outbox.poll-interval/outbox.grace/outbox.batch-size/outbox.max-attempts),由OutboxRelayConfig.bind绑定。ICompensationHandler.actionName()必须等于对应IExternalRequirement.code(),二者靠该字符串跨进程路由,不得由类名推导。
下一步阅读
- 仓储总览:聚合根的持久化与版本对账
- 外部依赖声明:
@ExternalDependency元数据声明与IExternalRequirement运行时需求的区分 - Outbox 链路装配
- MyBatis 集成:
MybatisOutboxStore的使用