CommandHandlerExtension

CommandHandlerExtension 是将自定义 Java 逻辑注入 AuraBoot Command Pipeline 的主要扩展点。平台中的所有写操作 —— 创建、更新、删除、状态流转、自定义命令 —— 都经过 CommandExecutor,由其在 16 个 pipeline 阶段中进行分发(Load → Command Authorization → Schema Validate → Idempotency → Entitlement → SoD Check → State Check → Assert → Pre-Actions → Pre-Invariant → Auto Set → Field Map → Computed Fields → Handler → Post-Execution → Completion)。一个 CommandHandlerExtension 绑定到特定的 command type,并在 Handler 阶段 贡献逻辑。

概念

一个 handler 的作用域限定到单个 command type(通过 getCommandType() 返回的 namespace:command-name 匹配)。Pipeline 在 Handler 阶段调用 execute(CommandContext),由其返回结果对象(可为 null)。

平台对每个 command 默认只运行一个 primary handler —— 即 chainsAfterPrimary() 返回 false 的 handler 中 getPriority() 最高的那个(priority 越大越优先)。chainsAfterPrimary() 返回 true 的 handler 是 secondary:它不参与 primary 选择,而是在 primary 之后、同一个命令事务内运行,从而让下游插件为另一个插件已拥有的 command 追加行为。多个 secondary 按 getPriority() 降序执行。

execute 内的所有写副作用都参与由 CommandExecutor 持有的外层命令事务边界。不要吞掉异常 —— 详见常见陷阱

接口签名

package com.auraboot.framework.plugin.extension;

import org.pf4j.ExtensionPoint;
import java.util.Set;

public interface CommandHandlerExtension extends ExtensionPoint {

    /** 当前 handler 处理的 command type。格式 "namespace:command-name",例如 "billing:generate-invoice"。 */
    String getCommandType();

    /** 该 handler 能处理的全部 command type(import 期校验需要有限列表)。默认返回 {getCommandType()}。 */
    default Set<String> getSupportedCommandTypes() {
        String commandType = getCommandType();
        return commandType == null ? Set.of() : Set.of(commandType);
    }

    /** 执行命令。返回结果对象(可为 null)。 */
    Object execute(CommandContext context) throws Exception;

    /** 是否支持给定的 command type,默认按 getCommandType() 精确匹配。 */
    default boolean supports(String commandType) {
        return getCommandType().equals(commandType);
    }

    /** handler 优先级,越高越先执行,默认 0。 */
    default int getPriority() {
        return 0;
    }

    /** 是否作为 secondary 在 primary 之后链式执行,默认 false(参与 primary 选择)。 */
    default boolean chainsAfterPrimary() {
        return false;
    }

    /** 在 dryRun 下是否安全执行,默认 false(dry-run 时跳过该 handler)。 */
    default boolean supportsDryRun() {
        return false;
    }
}

CommandContext 是一个 record,字段通过 record accessor 访问:ctx.tenantId()ctx.commandType()ctx.modelCode()ctx.recordId()ctx.payload()Map<String, Object>)、ctx.settings()Map<String, Object>)、ctx.pluginId()ctx.namespace()ctx.dryRun()。数据访问通过 ctx.dataAccessor()(或 ctx.settings().get("__dataAccessor"))拿到平台注入的 DataAccessor;还有 ctx.aiProviderAccessor()ctx.fileAccessor()ctx.asyncTaskAccessor() 等桥接器。

实现示例

下面的 handler 处理 crm:create_lead 命令,读取记录、计算分数并写回:

package com.acme.crm.handler;

import com.auraboot.framework.plugin.extension.CommandHandlerExtension;
import com.auraboot.framework.plugin.extension.DataAccessor;
import org.pf4j.Extension;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Map;

@Extension
public class LeadScoringHandler implements CommandHandlerExtension {

    @Autowired private LeadScoringService scoring;

    @Override
    public String getCommandType() {
        return "crm:create_lead";
    }

    @Override
    public Object execute(CommandContext ctx) throws Exception {
        DataAccessor db = ctx.dataAccessor();
        Map<String, Object> payload = ctx.payload();

        Integer score = scoring.score(payload);
        payload.put("lead_score", score);

        return Map.of("recordId", ctx.recordId(), "score", score);
    }
}

注册

AuraBoot 使用基于 Spring 的 PF4J 风格扩展发现(@Extension)。两个步骤:

  1. 在类上使用 @Extension(来自 org.pf4j)注解。
  2. META-INF/extensions.idx 列出扩展类,由 org.pf4j:pf4j 注解处理器(annotationProcessor 'org.pf4j:pf4j')在编译期自动生成。

plugin.json 中,确保扩展所在的包包含于插件类扫描范围。handler 与 command type 的绑定基于接口 + 注解 + getCommandType()(运行时再经 supports(...) 分发),而非通过 commands.jsonbindingRules.json 中的 handler 引用。

如需调用其他服务,使用标准的 Spring @Autowired。Plugin classloader 会通过宿主的 ApplicationContext 解析平台 bean。

常见陷阱

  • 吞掉异常。 在 pipeline 持有活动事务的情况下,execute 中的 catch (Exception e) { log.warn(...) } 会导致 rollback-only 标记但不抛出错误 —— 见红线 §8。重新抛出,或在带外副作用上使用 @Transactional(NOT_SUPPORTED)
  • commands.json 中的 inline bindingRules 不会被 importer 加载 —— 见红线 §6。Handler 与 command type 的绑定完全通过 getCommandType() 完成。
  • dry-run 下的外部副作用。 事务回滚只撤销 JDBC 写;HTTP/邮件/MQ/对象存储等外部副作用会逃出回滚边界。supportsDryRun() 默认 false,dry-run 时该 handler 直接被跳过;返回 true 前必须自查 ctx.dryRun() 并短路所有外部调用。
  • execute 中执行长耗时 I/O。 你正占用请求线程和事务。请发布到 outbox 或经 ctx.asyncTaskAccessor() 异步处理。

相关