Feature Description
The CommandHandlerRegistry, EventHandlerRegistry, and QueryHandlerRegistry have methods to register a CommandHandler, EventHandler, and QueryHandler respectively for a set of QualifiedNames.
Right now, there is no easy way to use these operations by the user other than invoking it themselves.
This manual invocation would be applicable when handling components are constructed in a declarative fashion.
It would, however, be beneficial if we can open up this subscription approach through additional means.
If the registration of a message handler, more specifically in the automated flow, would be able to deduce this set of QualifiedNames it is applicable to.
We can word this as discovering the "read compatibility" of a message handler.
For example, assume an event handler that is interested in AddressChanged events.
These events may happen to come in several forms in an domain, like UserMoved and AddressChanged, it would be beneficial if this one handler could cover both those message types.
Past versions of Axon Framework allowed this for event handlers specifically, where users could handle a parent class or interface of events.
The support for Axon Framework 5 should be an enhancement of this, dropping the hard necessity on reflection.
However, we do expect the old behavior to still be an option; it should simply be expanded.
With option like sealed interfaces or annotation-specific definitions of a set (on either the handler or the payload).
As a higher level version of this, we could introduce pattern-based matching in message handler annotations.
Current Behaviour
Axon Framework 5 introduced MessageType (combination of QualifiedName and version) to decouple message identification from Java payload types.
While this provides valuable flexibility (e.g., different microservices can have different implementations of the same logical message), it removes AF4's ability to match handlers based on Java type hierarchy.
In Axon Framework 4:
- Handler matching used
Message#getPayloadType and Java's type system
- A handler with a superclass/interface parameter matched all implementing/extending event types
- Example:
@EventHandler void handle(OrderEvent event) matched OrderPlaced, OrderShipped, etc.
In Axon Framework 5:
- Handler matching uses exact
QualifiedName comparison (defaults to Class#getName)
- Each event's
QualifiedName must be explicitly registered to a handler method
- The same handler cannot match multiple
QualifiedName values, even if their payload types share a common supertype
This prevents design patterns where a single method receives multiple event types and performs language-level dynamic dispatch (e.g., switch-case on sealed types, Decider pattern).
Wanted Behaviour
Extend the (automated and/or declarative) handler subscription flow such that a handler can be made read compatible with several message types.
Possible Workarounds
Current: Create separate @EventHandler methods for each event type, duplicating routing logic that could be handled by language features.
Notes
Framework-only Change: This feature requires changes only within Axon Framework, not Axon Server. The event stream filtering happens at the framework level during handler method selection.
Pattern-matching suggestions
An example of this, is adjusting the eventName property in @EventHandler annotation to support pattern matching against QualifiedName values. This maintains the benefits of AF5's MessageType system while restoring AF4's flexibility for multi-event handlers.
ATTENTION: How to implement that it's not already discussed. What you see below, it's just an idea.
Namespace Matching:
@EventHandler(eventName = "com.example.order.*")
public void evolve(OrderEvent event) {
// Matches all events with QualifiedName starting with "com.example.order."
}
Type Pattern Matching:
@EventHandler(eventName = "*.OrderPlaced|*.OrderShipped")
public void evolve(DomainEvent event) {
// Matches specific event QualifiedNames across namespaces
}
Any Match (Decider Pattern):
@EventHandler(eventName = "*")
public void evolve(DomainEvent event) {
switch (event) {
case OrderPlaced e -> evolve(e);
case OrderShipped e -> evolve(e);
default -> log.warn("Unhandled event: {}", event);
}
}
Event Sourcing with Pattern Matching:
@EventSourcingHandler(eventName = "com.example.order.*")
public void evolve(OrderEvent event) {
// Single sourcing handler for all order event QualifiedNames
// Dynamic dispatch based on sealed type hierarchy
}
The matcher should:
- Operate on
QualifiedName strings, not Java payload types
- Default to exact
QualifiedName match (maintains backward compatibility)
- Support namespace wildcards (
com.example.*)
- Support type alternatives (
TypeA|TypeB)
- Support "any" matcher (
*) for catch-all handlers
- Work seamlessly with custom
QualifiedName values (not derived from Class#getName)
Feature Description
The
CommandHandlerRegistry,EventHandlerRegistry, andQueryHandlerRegistryhave methods to register aCommandHandler,EventHandler, andQueryHandlerrespectively for a set ofQualifiedNames.Right now, there is no easy way to use these operations by the user other than invoking it themselves.
This manual invocation would be applicable when handling components are constructed in a declarative fashion.
It would, however, be beneficial if we can open up this subscription approach through additional means.
If the registration of a message handler, more specifically in the automated flow, would be able to deduce this set of
QualifiedNamesit is applicable to.We can word this as discovering the "read compatibility" of a message handler.
For example, assume an event handler that is interested in
AddressChangedevents.These events may happen to come in several forms in an domain, like
UserMovedandAddressChanged, it would be beneficial if this one handler could cover both those message types.Past versions of Axon Framework allowed this for event handlers specifically, where users could handle a parent class or interface of events.
The support for Axon Framework 5 should be an enhancement of this, dropping the hard necessity on reflection.
However, we do expect the old behavior to still be an option; it should simply be expanded.
With option like
sealed interfacesor annotation-specific definitions of a set (on either the handler or the payload).As a higher level version of this, we could introduce pattern-based matching in message handler annotations.
Current Behaviour
Axon Framework 5 introduced
MessageType(combination ofQualifiedNameand version) to decouple message identification from Java payload types.While this provides valuable flexibility (e.g., different microservices can have different implementations of the same logical message), it removes AF4's ability to match handlers based on Java type hierarchy.
In Axon Framework 4:
Message#getPayloadTypeand Java's type system@EventHandler void handle(OrderEvent event)matchedOrderPlaced,OrderShipped, etc.In Axon Framework 5:
QualifiedNamecomparison (defaults toClass#getName)QualifiedNamemust be explicitly registered to a handler methodQualifiedNamevalues, even if their payload types share a common supertypeThis prevents design patterns where a single method receives multiple event types and performs language-level dynamic dispatch (e.g., switch-case on sealed types, Decider pattern).
Wanted Behaviour
Extend the (automated and/or declarative) handler subscription flow such that a handler can be made read compatible with several message types.
Possible Workarounds
Current: Create separate
@EventHandlermethods for each event type, duplicating routing logic that could be handled by language features.Notes
Framework-only Change: This feature requires changes only within Axon Framework, not Axon Server. The event stream filtering happens at the framework level during handler method selection.
Pattern-matching suggestions
An example of this, is adjusting the
eventNameproperty in@EventHandlerannotation to support pattern matching againstQualifiedNamevalues. This maintains the benefits of AF5'sMessageTypesystem while restoring AF4's flexibility for multi-event handlers.ATTENTION: How to implement that it's not already discussed. What you see below, it's just an idea.
Namespace Matching:
Type Pattern Matching:
Any Match (Decider Pattern):
Event Sourcing with Pattern Matching:
The matcher should:
QualifiedNamestrings, not Java payload typesQualifiedNamematch (maintains backward compatibility)com.example.*)TypeA|TypeB)*) for catch-all handlersQualifiedNamevalues (not derived fromClass#getName)