Entity Identifier Resolution
Before invoking a command handler on an entity, Axon resolves the identifier of the entity instance the command targets.
An EntityIdResolver performs this step: given a message, it returns the entity identifier.
|
Entity identifier resolution is separate from command routing.
The routing key (see Command routing) selects the application instance that receives a command.
The |
The EntityIdResolver
EntityIdResolver<ID> is a functional interface with a single method:
ID resolve(Message message, ProcessingContext context) throws EntityIdResolutionException;
The returned identifier is used to load the entity from the StateManager.
The identifier can be of any type, as long as it matches the identifier type of the entity.
When no identifier can be resolved, the resolver throws an EntityIdResolutionException.
The command then fails without invoking a handler.
Axon uses an EntityIdResolver for:
-
the command handlers of event-sourced and state-based entities.
-
@InjectEntityparameters on stateful command handlers.
Built-in resolvers
| Resolver | Resolves the identifier from |
|---|---|
|
The payload member (field or method) annotated with |
|
A named payload property.
Used for the |
|
An entry in the message metadata. |
|
A primary resolver, with a secondary resolver as fallback. |
Annotation-based resolution
The AnnotationBasedEntityIdResolver reads the payload members annotated with @TargetEntityId, as described in Entity identification.
Several members may carry the annotation, as long as they result in exactly one distinct, non-null value.
Otherwise, it throws an EntityIdResolutionException.
Property-based resolution
The PropertyBasedEntityIdResolver reads the payload field named by its property argument, or the matching method (get<Property> or <Property>).
It throws an EntityIdResolutionException when the value is null.
Setting @InjectEntity(idProperty = "courseId") constructs this resolver.
Metadata-based resolution
The MetadataEntityIdResolver reads the identifier from the message metadata.
Use it when the payload does not carry the entity identifier, for example when the message originates from another model and the target identifier travels as metadata.
EntityIdResolver<String> metadataResolver() {
return MetadataEntityIdResolver.forKey("courseId"); (1)
}
| 1 | Resolves the identifier from the courseId metadata entry.
An absent or blank entry results in an EntityIdResolutionException. |
Metadata values are String instances, so forKey(String) resolves a String identifier.
For any other identifier type, pass the type and a Converter:
EntityIdResolver<UUID> convertingMetadataResolver(Converter converter) {
return MetadataEntityIdResolver.forKey("courseId", UUID.class, converter); (1)
}
| 1 | Converts the courseId metadata value to a UUID. |
Fallback resolution
The FallbackEntityIdResolver combines two resolvers.
It invokes the secondary resolver only when the primary resolver throws an EntityIdResolutionException.
Other exceptions of the primary resolver propagate.
EntityIdResolver<String> fallbackResolver() {
return new FallbackEntityIdResolver<>(
new AnnotationBasedEntityIdResolver<>(), (1)
MetadataEntityIdResolver.forKey("courseId") (2)
);
}
| 1 | The primary resolver reads the @TargetEntityId member of the payload. |
| 2 | The secondary resolver reads the courseId metadata entry. |
A FallbackEntityIdResolver is an EntityIdResolver itself, so fallbacks can be nested.
Custom resolvers
Implement EntityIdResolver when no built-in resolver fits, for example when the identifier is composed of several fields.
public class FacultyCourseIdResolver implements EntityIdResolver<String> {
@Override
public String resolve(Message message, ProcessingContext context) throws EntityIdResolutionException {
if (!(message.payload() instanceof EnrollInCourse command)) { (1)
throw new EntityIdResolutionException(
"Unable to resolve a course identifier for message [" + message.type() + "]."
); (2)
}
return command.facultyId() + "/" + command.courseId(); (3)
}
public record EnrollInCourse(String facultyId, String courseId, String studentId) {
}
}
| 1 | Checks whether the payload is of a type this resolver supports. |
| 2 | Throws an EntityIdResolutionException when no identifier can be resolved.
A FallbackEntityIdResolver relies on this exception to select its secondary resolver. |
| 3 | Composes the identifier from two payload fields. |
Configuring the resolver of an entity
The way to set a resolver depends on how the entity is defined:
| Entity definition | Resolver configuration |
|---|---|
|
The |
Declarative entity |
The |
Annotated entity ( |
The |
The EntityIdResolverDefinition
An annotated entity does not construct its EntityIdResolver itself.
Axon asks an EntityIdResolverDefinition to create it.
The definition receives the entity type, the identifier type, the entity metamodel, and the application Configuration:
public class MetadataFallbackEntityIdResolverDefinition implements EntityIdResolverDefinition {
@Override
public <E, ID> EntityIdResolver<ID> createIdResolver(Class<E> entityType,
Class<ID> idType,
EntityMetamodel<E> entityMetamodel,
Configuration configuration) {
return new FallbackEntityIdResolver<>( (1)
new AnnotationBasedEntityIdResolver<>(),
MetadataEntityIdResolver.forKey("entityId", idType, configuration.getComponent(Converter.class))
);
}
}
| 1 | Creates a resolver that reads the @TargetEntityId payload member first, and the entityId metadata entry second.
The Configuration provides the Converter required to convert the metadata value to the identifier type. |
By default, an annotated entity uses a definition that creates the AnnotationBasedEntityIdResolver.
Set the entityIdResolverDefinition attribute to use another definition for a single entity:
@EventSourcedEntity(
tagKey = "courseId",
entityIdResolverDefinition = MetadataFallbackEntityIdResolverDefinition.class (1)
)
public class CourseWithExplicitDefinition {
// Entity state and handlers omitted.
}
| 1 | The definition class requires a public no-argument constructor. |
Application-wide definition
Register an EntityIdResolverDefinition as a component to change the default for all annotated entities in the application.
Entities that name their own definition on the annotation keep it.
-
Spring Boot
-
Plain Java
@Configuration
public class SpringApplicationWideDefinitionConfiguration {
@Bean
public EntityIdResolverDefinition entityIdResolverDefinition() {
return new MetadataFallbackEntityIdResolverDefinition(); (1)
}
}
| 1 | Applies to every annotated entity that does not set entityIdResolverDefinition. |
public static EventSourcingConfigurer configure(EventSourcingConfigurer configurer) {
return configurer.componentRegistry(registry -> registry.registerComponent(
EntityIdResolverDefinition.class, (1)
config -> new MetadataFallbackEntityIdResolverDefinition()
));
}
| 1 | Applies to every annotated entity that does not set entityIdResolverDefinition. |
Axon selects the definition of an annotated entity in this order:
-
The definition set on the
entityIdResolverDefinitionattribute of the annotation. -
The
EntityIdResolverDefinitionregistered as a component. -
The default
EntityIdResolverDefinitionthat creates theAnnotationBasedEntityIdResolver.
|
The application-wide definition applies to annotated entities only.
Declarative entities set their resolver explicitly, and |