Axon 参考指南
  • 介绍
  • 架构概览
    • DDD & CQRS 概念
    • 事件溯源
    • 事件驱动的微服务
  • Axon Server
  • 发行说明
    • Axon Framework
      • Major Releases
      • Minor Releases
    • Axon Server
      • Major Releases
      • Minor Releases Standard Edition
      • Minor Releases Enterprise Edition
    • Axon Framework Extensions
      • AMQP
        • Major Releases
      • CDI
        • Major Releases
      • JGroups
        • Major Releases
      • Kafka
        • Major Releases
        • Minor Releases
      • Kotlin
        • Experimental Releases
      • Mongo
        • Major Releases
        • Minor Releases
      • Reactor
        • Major Releases
        • Minor Releases
      • Spring Cloud
        • Major Releases
        • Minor Releases
      • Tracing
        • Major Releases
        • Minor Releases
  • Getting Started
    • 快速开始
  • Axon Framework
    • 介绍
    • 消息传递概念
      • 消息剖析
      • 消息关联
      • 消息拦截
      • 支持带注解的处理程序
      • 异常处理
      • 工作单元
    • 命令
      • 建模
        • 聚合
        • 多实体聚合
        • 聚合状态存储
        • 从另一个聚合创建聚合
        • 聚合多态性
        • 解决冲突
      • 命令调度器
      • 命令处理程序
      • 基础设施
      • 配置
    • 事件
      • 事件调度器
      • 事件处理程序
      • 事件处理器
        • 订阅事件处理器
        • 流式事件处理器
      • 事件总线和事件存储
      • 事件版本控制
    • 查询
      • 查询处理
      • 查询调度器
      • 查询处理程序
      • 实现
      • 配置
    • 长时处理过程(Sagas)
      • 实现
      • 关联
      • 基础设施
    • Deadlines
      • Deadline Managers
      • Event Schedulers
    • 测试
      • 命令 / 事件
      • 长时处理过程(Sagas)
    • 序列化
    • 调整
      • 事件快照
      • 事件处理
      • 命令处理
    • 监控和指标
    • Spring Boot 集成
    • 模块
  • Axon Server
    • 介绍
    • 安装
      • 本地安装
        • Axon Server SE
        • Axon Server EE
      • Docker / K8s
        • Axon Server SE
        • Axon Server EE
    • 管理
      • 配置
        • System Properties
        • Command Line Interface
        • REST API
        • GRPC API
      • Monitoring
        • Actuator Endpoints
        • gRPC Metrics
        • Heartbeat Monitoring
      • Clusters
      • Replication Groups
      • Multi-Context
      • Tagging
      • Backup and Messaging-only Nodes
      • Backups
      • Recovery
      • Plugins
      • Error Codes
    • 安全
      • SSL
      • 访问控制
      • 访问控制 - 标准版
      • 访问控制 - 企业版
      • 访问控制 - 客户端应用程序
      • 访问控制 - 命令行
      • 访问控制 - REST API
      • 访问控制 - LDAP
      • 访问控制 - OAuth 2.0
    • 性能
      • 事件段
      • 流量控制
    • 迁移
      • Standard to Enterprise Edition
      • Non-Axon Server to Axon Server
  • Extensions
    • Spring AMQP
    • JGroups
    • Kafka
    • Kotlin
    • Mongo
    • Reactor
      • Reactor Gateways
    • Spring Cloud
    • Tracing
  • Appendices
    • A. RDBMS Tuning
    • B. Message Handler Tuning
      • 参数解析器
      • 处理程序增强
    • C. 元数据注解
    • D. 标识符生成
    • E. Axon Server Query Language
由 GitBook 提供支持
在本页
  • Aggregate Command Handlers
  • Business Logic and State Changes
  • Applying Events from Event Sourcing Handlers
  • Aggregate Command Handler Creation Policy
  • External Command Handlers
  1. Axon Framework
  2. 命令

命令处理程序

Command Handlers

上一页命令调度器下一页基础设施

最后更新于2年前

Aggregate Command Handlers

Although Command Handlers can be placed in regular components, it is recommended to define the Command Handlers directly on the Aggregate that contains the state to process this command.

To define a Command Handler in an Aggregate, simply annotate the method which should handle the command with @CommandHandler. The @CommandHandler annotated method will become a Command Handler for Command Messages where the command name matches fully qualified class name of the first parameter of that method. Thus, a method signature of void handle(RedeemCardCommand cmd) annotated with @CommandHandler, will be the Command Handler of the RedeemCardCommand Command Messages.

Command Messages can also be with different command names. To be able to handle those correctly, the String commandName value can be specified in the @CommandHandler annotation.

In order for Axon to know which instance of an Aggregate type should handle the Command Message, the property carrying the Aggregate Identifier in the command object must be annotated with @TargetAggregateIdentifier. The annotation may be placed on either the field or an accessor method (e.g. a getter) in the Command object.

Routing in a distributed environment

Regardless of the type of command, as soon as you start distributing your application (through Axon Server, for example), it is recommended to specify a routing key on the command. This is the job of the @TargetAggregateIdentifier, but in absence of a field worthy of the annotation, the @RoutingKey annotation should be added to ensure the command can be routed.

If neither annotation works for your use case, a different RoutingStrategy can be configured, as is explained in the section.

Taking the GiftCard Aggregate as an example, we can identify two Command Handlers on the Aggregate:

import org.axonframework.commandhandling.CommandHandler;
import org.axonframework.modelling.command.AggregateIdentifier;

import static org.axonframework.modelling.command.AggregateLifecycle.apply;

public class GiftCard {

    @AggregateIdentifier
    private String id;
    private int remainingValue;

    @CommandHandler
    public GiftCard(IssueCardCommand cmd) {
        apply(new CardIssuedEvent(cmd.getCardId(), cmd.getAmount()));
    }

    @CommandHandler
    public void handle(RedeemCardCommand cmd) {
        if (cmd.getAmount() <= 0) {
            throw new IllegalArgumentException("amount <= 0");
        }
        if (cmd.getAmount() > remainingValue) {
            throw new IllegalStateException("amount > remaining value");
        }
        apply(new CardRedeemedEvent(id, cmd.getTransactionId(), cmd.getAmount()));
    }
    // omitted event sourcing handlers
}

The Command objects, IssueCardCommand and RedeemCardCommand, which GiftCard handles have the following format:

import org.axonframework.modelling.command.TargetAggregateIdentifier;

public class IssueCardCommand {

    @TargetAggregateIdentifier
    private final String cardId;
    private final Integer amount;

    public IssueCardCommand(String cardId, Integer amount) {
        this.cardId = cardId;
        this.amount = amount;
    }
    // omitted getters, equals/hashCode, toString functions
}

public class RedeemCardCommand {

    @TargetAggregateIdentifier
    private final String cardId;
    private final String transactionId;
    private final Integer amount;

    public RedeemCardCommand(String cardId, String transactionId, Integer amount) {
        this.cardId = cardId;
        this.transactionId = transactionId;
        this.amount = amount;
    }
    // omitted getters, equals/hashCode, toString functions
}

The cardId present in both commands is the reference to a GiftCard instance and thus is annotated with the @TargetAggregateIdentifier annotation. Commands that create an Aggregate instance do not need to identify the target aggregate identifier, as there is no Aggregate in existence yet. It is nonetheless recommended for consistency to annotate the Aggregate Identifier on them as well.

If you prefer to use another mechanism for routing commands, the behavior can be overridden by supplying a custom CommandTargetResolver. This class should return the Aggregate Identifier and expected version (if any) based on a given command.

Aggregate Creation Command Handlers

When the @CommandHandler annotation is placed on an aggregate's constructor, the respective command will create a new instance of that aggregate and add it to the repository. Those commands do not require to target a specific aggregate instance. Therefore, those commands need neither the @TargetAggregateIdentifier nor the @TargetAggregateVersion annotation. Furthermore, a custom CommandTargetResolver will not be invoked for these commands.

Business Logic and State Changes

Within an Aggregate there is a specific location to perform business logic validation and Aggregate state changes. The Command Handlers should decide whether the Aggregate is in the correct state. If yes, an Event is published. If not, the Command might be ignored or an exception could be thrown, depending on the needs of the domain.

State changes should not occur in any Command Handling function. The Event Sourcing Handlers should be the only methods where the Aggregate's state is updated. Failing to do so means the Aggregate would miss state changes when it is being sourced from it's events.

When to handle an Event

The only state an Aggregate requires is the state it needs to make a decision. Handling an Event published by the Aggregate is thus only required if the state change the Event resembles is needed to drive future validation.

Applying Events from Event Sourcing Handlers

It is possible to apply() new events inside an Event Sourcing Handler method. This makes it possible for an Entity 'B' to apply an event in reaction to Entity 'A' doing something. Axon will ignore the apply()invocation when replaying historic events upon sourcing the given Aggregate. Do note that in the scenario where Event Messages are published from an Event Sourcing Handler, the Event of the inner apply() invocation is only published to the entities after all entities have received the first event. If more events need to be published, based on the state of an entity after applying an inner event, use apply(...).andThenApply(...).

Reacting to other Events

An Aggregate cannot handle events from other sources then itself. This is intentional as the Event Sourcing Handlers are used to recreate the state of the Aggregate. For this it only needs it's own events as those represent it's state changes.

Aggregate Command Handler Creation Policy

Up until now, we have depicted the GiftCard aggregate with roughly two types of command handlers:

  1. @CommandHandler annotated constructors

  2. @CommandHandler annotated methods

Option 1 will always expect to be the instantiation of the GiftCard aggregate, whilst option 2 expects to be targeted towards an existing aggregate instance. Although this may be the default, there is the option to define a creation policy on a command handler. This can be achieved by adding the @CreationPolicy annotation to a command handler annotated method, like so:

import org.axonframework.commandhandling.CommandHandler;
import org.axonframework.modelling.command.CreationPolicy;
import org.axonframework.modelling.command.AggregateCreationPolicy;

public class GiftCard {

    public GiftCard() {
        // Required no-op constructor
    }

    @CommandHandler
    @CreationPolicy(AggregateCreationPolicy.ALWAYS)
    public void handle(IssueCardCommand cmd) {
        // An `IssueCardCommand`-handler which will create a `GiftCard` aggregate 
    }

    @CommandHandler
    @CreationPolicy(AggregateCreationPolicy.CREATE_IF_MISSING)
    public void handle(CreateOrRechargeCardCommand cmd) {
        // A 'CreateOrRechargeCardCommand'-handler which creates a `GiftCard` aggregate if it did not exist
        // Otherwise, it will update an existing `GiftCard` aggregate.
    }
    // omitted aggregate state, command handling logic and event sourcing handlers
}

As is shown above, the @CreationPolicy annotation requires stating the AggregateCreationPolicy. This enumeration has the following options available:

  • ALWAYS - A creation policy of "always" will expect to instantiate the aggregate. This effectively works like a command handler annotated constructor. Without defining a return type, the aggregate identifier used during the creation will be returned. Through this approach, it is possible to return other results next to the aggregate identifier.

  • CREATE_IF_MISSING - A creation policy of "create if missing" can either create an aggregate or act on an existing instance.

    This policy should be regarded as a create or update approach of an aggregate.

  • NEVER - A creation policy of "never" will be handled on an existing aggregate instance.

    This effectively works like any regular command handler annotated method.

External Command Handlers

A Command Handling Object is a simple (regular) object, which has @CommandHandler annotated methods. Unlike with Aggregates, there is only a single instance of a Command Handling Object, which handles all commands of the types it declares in its methods:

import org.axonframework.commandhandling.CommandHandler;
import org.axonframework.modelling.command.Repository;

public class GiftCardCommandHandler {

    // 1.
    private final Repository<GiftCard> giftCardRepository;

    @CommandHandler
    public void handle(RedeemCardCommand cmd) {
        giftCardRepository.load(cmd.getCardId()) // 2.
                          .execute(giftCard -> giftCard.handle(cmd)); // 3.
    }

    // omitted constructor
}

In the above snippet we have decided that the RedeemCardCommand should no longer be directly handled on the GiftCard. Instead, we load the GiftCard manually and execute the desired method on it:

  1. The Repository for the GiftCard Aggregate, used for retrieval and storage of an Aggregate.

    If @CommandHandler methods are placed directly on the Aggregate, Axon will automatically know to call the Repository to load a given instance.

  2. To load the intended GiftCard Aggregate instance, the Repository#load(String) method is used.

    The provided parameter should be the Aggregate identifier.

  3. After that Aggregate has been loaded, the Aggregate#execute(Consumer) function should be invoked to perform an operation on the Aggregate.

    Using the execute function ensure that the Aggregate life cycle is correctly started.

The will guard from unintentional state changes in Command Handling functions. It is thus advised to provide thorough test cases for any Aggregate implementation.

In some cases, especially when the Aggregate structure grows beyond just a couple of Entities, it is cleaner to react on events being published in other Entities of the same Aggregate (multi Entity Aggregates are explained in more detail ). However, since the Event Handling methods are also invoked when reconstructing Aggregate state, special precautions must be taken.

To make an Aggregate react on events from other Aggregate instances, or should be leveraged

Command handling functions are most often directly placed on the Aggregate (as described in more detail ). There are situations however where it is not possible nor desired to route a command directly to an Aggregate instance. Message handling functions, like Command Handlers, can however be placed on any object. It is thus possible to instantiate a 'Command Handling Object'.

It is thus not mandatory to directly access the Repository, but a .

Aggregate Test Fixture
here
Sagas
Event Handling Components
here
dispatched
design choice
Routing Strategy