Skip to content

应用服务(Application Service) ​

本文档说明 io.pragmatic.ddd.application 包提供的应用服务编排能力:命令执行器、工作单元、应用服务基类、实体工厂/更新器/属性解析器,以及 Outbox 事务性发件箱。前置阅读:领域建模 · 业务规则引擎 · 领域事件。

1. 概述 ​

1.1 核心定位 ​

应用服务层负责编排"领域逻辑 → 规则校验 → 持久化 → 事件分发"的标准流程,并对外部调用屏蔽模板细节。框架提供两类编排入口与一个便捷基类:

  • 单聚合根命令:一次操作只涉及一个聚合根,使用 CommandExecutor。
  • 跨聚合根工作单元:一次操作涉及多个聚合根、需统一提交,使用 UnitOfWork。
  • 应用服务基类:AbstractApplicationService 内聚二者,提供语义化方法。

此外,Outbox 模式(application.outbox)提供事务性发件箱,保证事件与聚合根同事务原子落库。

两者均提供零副作用的试跑(Dry-run)入口,用于预览校验结论而不落库、不发事件。

1.2 概念层级与依赖关系 ​

text
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 / CommandExecutorio.pragmatic.ddd.application单聚合根命令(默认:同事务 save,提交后 publish)
IUnitOfWork / AbstractUnitOfWork / UnitOfWorkio.pragmatic.ddd.application跨聚合根工作单元(默认:统一 save 后 publishList)
AbstractApplicationServiceio.pragmatic.ddd.application应用服务便捷基类
DryRunResultio.pragmatic.ddd.application试跑(Dry-run)结构化结果(record)
EntityFactory / EntityUpdaterio.pragmatic.ddd.application实体创建 / 修改契约
IEntityPropertyResolver / EntityPropertyResolversio.pragmatic.ddd.application实体属性解析契约与适配器工厂
OutboxCommandExecutor / OutboxUnitOfWorkio.pragmatic.ddd.application.outbox事务性发件箱执行器
EagerOutboxPublisher / OutboxRelay / OutboxRelayConfigio.pragmatic.ddd.application.outbox主动推送 / 兜底轮询 / 运行配置
OutboxStatus / OutboxMessage / OutboxEntryio.pragmatic.ddd.application.outbox发件箱状态机与载体
CompensableCommandExecutorio.pragmatic.ddd.application.compensation可补偿命令执行器(装饰器,为声明外部需求的命令叠加逆序补偿)
CompensationActionRegistryio.pragmatic.ddd.application.compensation可补偿动作注册表(按需求类型建索引,注册期查重)
CompensationOptions / CompensationPolicyio.pragmatic.ddd.application.compensation补偿模式(inMemory / durableMode)与单动作重试策略
ICompensableActionio.pragmatic.ddd.application.compensation正向执行 / 回写聚合 / 逆向补偿动作契约
IExternalRequirementio.pragmatic.ddd.base外部需求契约:聚合声明、触发补偿的运行时凭证(L3 路由键 = code())
ICompensationLogio.pragmatic.ddd.application.compensation.spi持久化补偿日志 SPI(WAL,REQUIRES_NEW 短事务)
ICompensationHandlerio.pragmatic.ddd.application.compensation.spi崩溃兜底补偿执行器(按 actionName() 编码路由)
CompensationRelayio.pragmatic.ddd.application.compensation.relay补偿兜底中继(回收超时认领 / 重试 / 死信)

2. 核心概念详解 ​

2.1 单聚合根命令:ICommandExecutor / CommandExecutor ​

AbstractCommandExecutor 固定五步模板,CommandExecutor(默认)在 persistAndDispatch 钩子中同一事务内落库、事务提交后再发布事件:

text
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)。

java
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 时视为无规则约束;仅规则类异常被转译为未通过,其余异常照常上抛。

java
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 固定多聚合根统一提交流程,划分为三阶段:领域逻辑与规则校验在事务外完成,持久化落在独立的数据库事务内,事件发布在事务提交之后:

text
阶段一(事务外)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(含同一聚合的多次操作)必然落在同一个数据库事务内,任一条目失败整体回滚。

java
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。

java
DryRunResult result = unitOfWork.tryCommit();   // 零副作用,返回聚合全部条目校验结论

2.3 应用服务基类:AbstractApplicationService ​

应用服务基类,内聚 ICommandExecutor 与 IUnitOfWork 工厂;继承者必须显式注入命令执行器与工作单元工厂,以明确声明自身的一致性语义。子类以语义方法暴露能力,无需直接持有编排器。

java
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>:创建新实体 ​

java
public interface EntityFactory<T extends AggregateRoot<?>, C> {
    T create(C command);   // 从 Command DTO 构建新聚合根,遵循"先算后赋"
}

EntityUpdater<T, C>:修改已有实体 ​

java
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,一处定义多处复用。

java
// 取数仅依赖 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 应用服务分层标记 ​

java
public interface ICommandApplicationService { }   // 命令服务(写操作)
public interface IQueryApplicationService { }      // 查询服务(读操作)

读写分离标记,配合仓储的 IRepository(写)与 IAggregateProjection(读)实现读写分离:

java
public class OrderCommandService extends AbstractApplicationService
        implements ICommandApplicationService { /* 写操作 */ }

public class OrderQueryService implements IQueryApplicationService { /* 读操作 */ }

2.6 可补偿命令执行器(分布式事务补偿) ​

CompensableCommandExecutor 是 ICommandExecutor 的装饰器:包装任意真实执行器(默认 / Outbox),为其中声明了外部需求的命令叠加「正向执行外部动作 → 失败逆序补偿」的能力。它把「跨本地事务边界的分布式补偿」收敛到应用层编排内,使一次用例既能落库发事件、又能对外部副作用做回滚,且不引入第二套事务语义。

text
CompensableCommandExecutor ──装饰──→ ICommandExecutor(默认 / Outbox)
   │  段一 领域逻辑(纯内存,收集外部需求)
   │  段二 逐项需求解析 + 补偿范围内正向执行 + 回写聚合
   │  段三 规则校验 → 持久化 → 事件分发(由 delegate 完成)
   └─ 整体由 CompensationTemplate 包裹:成功提交;任一环节失败按正向逆序补偿已执行动作并重抛

2.6.1 三段式执行顺序(事实依据) ​

CompensableCommandExecutor.execute 在 CompensationScopes.begin 打开的补偿范围内运行(CompensableCommandExecutor.java:53),模板(CompensationTemplate.call)固定「成功即提交、异常即逆序补偿并重抛原异常」:

text
段一(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>:一个「正向调用外部 + 逆向回滚外部」的配对单元,绑定所属聚合类型与需求类型——正向、回写、补偿一律从聚合读取业务事实。必须无状态(作为单例由容器管理)。
java
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 同构):

java
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)。
java
// 持久化模式示例:业务提供崩溃兜底执行器,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:

java
@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):

维度OutboxCommandExecutorCompensableCommandExecutor
解决的问题本地事件与聚合同事务原子落库、不丢事件本地事务之外的外部副作用(预占 / 扣减 / 发券)失败可回滚
关注边界库内:事件 vs 聚合库外:本聚合 vs 外部系统
是否引入第二套语义是(Outbox 一致性)否(仅装饰既有执行器)
典型组合单独用即可叠加在 OutboxCommandExecutor 上(推荐)

⚠️ 补偿不替代 Outbox:补偿解决「外部副作用回滚」,Outbox 解决「事件可靠投递」。下单既要可靠发事件又要回滚库存预占时,用 CompensableCommandExecutor 装饰 OutboxCommandExecutor。

⚠️ 只装饰、不得错配:被装饰的执行器与补偿执行器共享同一事务边界;不可把 CompensableCommandExecutor 装饰到两套不一致的执行器上(同 §2.3 语义一致约束)。

3. 关键机制与避坑指南 ​

3.1 Outbox 事务性发件箱 ​

OutboxCommandExecutor / OutboxUnitOfWork 与默认执行器并存、零侵入,保证事件与聚合根同事务原子落库:

text
① 同事务:聚合落库 + outbox 落库(PENDING)
② 事务提交后 → EagerOutboxPublisher.publishAfterCommit 主动推送(post-commit,规避"提交前误发")
③ 若主动推送失败/崩溃(未 markSent)→ OutboxRelay 兜底轮询补偿

EagerOutboxPublisher 在事务外发送原始事件,成功后 markSent(PENDING→SENT,带状态守卫、幂等);失败/崩溃不标记,保留 PENDING 交由 Relay 补偿。

java
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。

text
PENDING ──claim──→ PROCESSING ──publish成功──→ SENT
                      │
                      └──publish失败──→ release → PENDING
                                        ↓
                                   attempts > max?
                                      ├── 是 → FAILED(死信)
                                      └── 否 → 继续 PENDING

OutboxRelay 通过 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 前缀从配置源绑定。

java
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
工作单元重复提交/试跑IllegalStateExceptioncommit() / 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.applyapply 非 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 / {语义}CommandExecutorICommandExecutor、CommandExecutor
工作单元接口/类IUnitOfWork / {语义}UnitOfWorkIUnitOfWork、UnitOfWork、OutboxUnitOfWork
应用服务基类Abstract{层}ServiceAbstractApplicationService
命令服务类{聚合}CommandService implements ICommandApplicationServiceOrderCommandService
查询服务类{聚合}QueryService implements IQueryApplicationServiceOrderQueryService
语义方法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 ICompensationHandlerReserveInventoryCompensationHandler
外部需求{语义}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(),二者靠该字符串跨进程路由,不得由类名推导。

下一步阅读