A state change within an application starts with a Command. A Command is a combination of expressed intent (which describes what you want done) as well as the information required to undertake action based on that intent. A Command Handler is responsible for handling commands of a certain type and taking action based on the information contained inside it.
The use of an explicit command dispatching mechanism has a number of advantages. First of all, there is a single object that clearly describes the intent of the client. By logging the command, you store both the intent and related data for future reference. Command handling also makes it easy to expose your command processing components to remote clients, via web services for example. Testing also becomes a lot easier, you could define test scripts by just defining the starting situation (given), command to execute (when) and expected results (then) by listing a number of events and commands (see Chapter 8, Testing). The last major advantage is that it is very easy to switch between synchronous and asynchronous command processing.
The next sections provide an overview of the tasks related to creating a Command Handling infrastructure with the Axon Framework.
The Command Handler is the object that receives a Command of a pre-defined type and
takes action based on its contents. In Axon, a Command may be any object. There is no
predefined type that needs to be implemented. The Command Handler, however, must
implement the CommandHandler
interface. This interface declares only a
single method: Object handle(T command, UnitOfWork uow)
, where T is the
type of Command this Handler can process. The concept of the UnitOfWork is explained in
Section 3.4, “Unit of Work”. It is not recommended to use return values, but they
are allowed. Always consider using a "fire and forget" style of command handlers, where
a client does not have to wait for a response. As return value in such a case, you are
recommended to use Void.TYPE
, the official representation of the
void
keyword.
Note | |
---|---|
Note that Command Handlers need to be explicitly subscribed to the Command Bus for the specific types of Command they can handle. See Section 3.3, “Configuring the Command Bus”. |
Annotation support
More often than not, a command handler will need to process several types of closely
related commands. With Axon's annotation support you can use any POJO as command
handler. Just add the @CommandHandler
annotation to your methods to turn
them into a command handler. These methods should declare the command to process as the
first parameter. They may take an optional second parameter, which is the
UnitOfWork
for that command (see Section 3.4, “Unit of Work”). Note
that for each command type, there may only be one handler! This restriction counts for
all handlers registered to the same command bus.
You can use the AnnotationCommandHandlerAdapter
to turn your annotated
class into a CommandHandler
. The adapter also takes a
CommandBus
instance. Use the subscribe()
method on the
adapter to subscribe all the annotated handlers to the command bus using the correct
command type.
Note | |
---|---|
If you use Spring, you can add the Note that you need to be careful when mixing manual wrapping and the use of annotation-config element. This might result in command handler being subscribed twice. |
The CommandBus provides two methods to dispatch commands to their respective handler:
dispatch(command, callback)
and dispatch(command)
. The
first parameter is the actual command to dispatch. The optional second parameter takes a
callback that allows the dispatching component to be notified when command handling
is completed. This callback has two methods: onSuccess()
and
onFailure()
, which are called when command handling returned normally,
or when it threw an exception, respectively.
The calling component may not assume that the callback is invoked in the same thread
that dispatched the command. If the calling thread depends on the result before
continuing (which is a highly discouraged approach), you can use the
FutureCallback
. It is a combination of a Future
(as
defined in the java.concurrent package) and Axon's CommandCallback
.
Best scalability is achieved when your application is not interested in the result of
a dispatched command at all. In that case, you should use the single-parameter
version of the dispatch
method. If the CommandBus
is fully
asynchronous, it will return immediately after the command has been successfully
received. Your application will just have to guarantee that the command is processed and
with "positive outcome", sooner or later...
The Command Bus is the mechanism that dispatches commands to their respective Command
Handler. Commands are always sent to only one (and exactly one) command handler. If no
command handler is available for a dispatched command, an exception
(NoHandlerForCommandException
) is thrown. Subscribing multiple command
handlers to the same command type will result in subscriptions replacing each other. In
that case, the last subscription wins.
Axon provides a single implementation of the Command Bus:
SimpleCommandBus
. The SimpleCommandBus
dispatches and
commands and executes the handler in the calling thread. You can subscribe and
unsubscribe command handlers using the subscribe
and
unsubscribe
methods, respectively. They both take two parameters: the
type of command to (un)subscribe the handler to, and the handler to (un)subscribe. An
unsubscription will only be done if the handler passed as the second parameter was
currently assigned to handle that type of command. If another command was subscribed to
that type of command, nothing happens.
The Unit of Work is an important concept in the Axon Framework. The processing of a command can be seen as a single unit. Each time a command handler performs an action, it is tracked in the current Unit of Work. When command handling is finished, the Unit of Work is committed and all actions are finalized. This means that any repositores are notified of state changes in their aggregates and events scheduled for publication are send to the Event Bus.
The Unit of Work serves two purposes. First, it makes the interface towards repositories a lot easier, since you do not have to explicitly save your changes. Secondly, it is an important hook-point for interceptors to find out what a command handler has done.
In most cases, you are unlikely to need access to the Unit of Work. It is mainly used
by the building blocks that Axon provides. If you do need access to it, for whatever
reason, there are a few ways to obtain it. The Command Handler receives the Unit Of Work
through a parameter in the handle method. If you use annotation support, you may add the
optional second parameter of type UnitOfWork
to your annotated method. In
other locations, you can retrieve the Unit of Work bound to the current thread by
calling CurrentUnitOfWork.get()
. Note that this method will throw an
exception if there is no Unit of Work bound to the current thread. Use
CurrentUnitOfWork.isStarted()
to find out if one is available.
Note | |
---|---|
Note that the Unit of Work is merely a buffer of changes, not a replacement for Transactions. Although all staged changes are only committed when the Unit of Work is committed, its commit is not atomic. That means that when a commit fails, some changes might have been persisted, while other are not. Best practices dictate that a Command should never contain more than one action. If you stick to that practice, a Unit of Work will contain a single action, making it safe to use as-is. If you have more actions in your Unit of Work, then you could consider attaching a transaction to the Unit of Work's commit. See Section 3.5.1, “Transaction management”. |
One of the advantages of using a command bus is the ability to undertake action based on all incoming commands. Examples are logging or authentication, which you might want to do regardless of the type of command. This is done using Command Handler Interceptors. These interceptors can take action both before and after command processing. Interceptors can even block command processing altogether, for example for security reasons.
Interceptors must implement the CommandHandlerInterceptor
interface. This
interface declares one method, handle
, that takes three parameters: the
command, the current UnitOfWork
and an InterceptorChain
. The
InterceptorChain
is used to continue the dispatching process.
The command handling process can be considered an atomic procedure; it should either be processed entirely, or not at all. Axon Framework uses the Unit Of Work to track actions performed by the command handlers. After the command handler completed, Axon will try to commit the actions registered with the Unit Of Work. This involves storing modified aggregates (see Chapter 4, Domain Modeling) in their respective repository (see Chapter 5, Repositories and Event Stores) and publishing events on the Event Bus (see Chapter 6, Event Processing).
The Unit Of Work, however, it is not a replacement for a transaction. The Unit Of Work only ensures that changes made to aggregates are stored upon successful execution of a command handler. If an error occurs while storing an aggregate, any aggregates already stored are not rolled back. If this is important to your application (although it should be avoided as much as possible), consider using a Transaction Interceptor on the command bus that attaches a transaction to the Unit of Work.
Axon provides the SpringTransactionalInterceptor
, which uses Spring's
PlatformTransactionManager
to manage the actual transactions. A
transaction is committed after a successful commit of the Unit of Work, or rolled
back as the Unit of Work is rolled back.
Well designed events will give clear insight in what has happened, when and why. To use the event store as an Audit Trail, which provides insight in the exact history of changes in the system, this information might not be enough. In some cases, you might want to know which user caused the change, using what command, from which machine, etc.
The AuditingInterceptor
is an interceptor that allows you to attach
arbitray information to events just before they are stored or published. The
AuditingInterceptor
uses an AuditingDataProvider
to
retrieve the information to attach to these events. You need to provide the
implementation of the AuditingDataProvider
yourself.
An Audit Logger may be configured to write to an audit log. To do so, you can
implement the AuditLogger
interface and configure it in the
AuditingInterceptor
. The audit logger is notified both on succesful
execution of the command, as well as when execution fails. If you use event
sourcing, you should be aware that the event log already contains the exact details
of each event. In that case, it could suffice to just log the event identifier or
aggregate identifier and sequence number combination.
Note | |
---|---|
Note that the log method is called in the same thread as the command processing. This means that logging to slow sources may result in higher response times for the client. When important, make sure logging is done asynchronously from the command handling thread. |