Skip to content

Make EntityModel decoratable - #4765

Closed
CodeDrivenMitch wants to merge 2 commits into
mainfrom
enhancement/decoratable-entity-metamodel
Closed

Make EntityModel decoratable#4765
CodeDrivenMitch wants to merge 2 commits into
mainfrom
enhancement/decoratable-entity-metamodel

Conversation

@CodeDrivenMitch

Copy link
Copy Markdown
Contributor

The AnnotatedEntityMetamodel is casting the EntityEvolver to an implementation; the AnnotatedEntityMetamodel. It's doing this because the AnnotatedEntityIdResolver needs access to the type information, which is an annotation-specific concern and should not be on the EntityEvolver interface.

The result was that the EntityEvolver could never be decorated, as it's impossible to satisfy the cast. This leads to the Platform client having to destruct and re-construct the EventSourcingRepository, creating very close coupling for something that should be easy.

This PR splits the annotation-specific functionality, the providing of class information about handlers, to the RepresentationResolvingEntityEvolver. Now, the EntityEvolver can be decorated, as long as decorators implement RepresentationResolvingEntityEvolver as well. The module configuration is no longer bound to an implementation, but to an interface, removing the brittle re-construct logic for Platform

The `AnnotatedEntityMetamodel` is casting the `EntityEvolver` to an implementation; the `AnnotatedEntityMetamodel`. It's doing this because the AnnotatedEntityIdResolver needs access to the type information, which is an annotation-specific concern and should not be on the EntityEvolver interface.

The result was that the `EntityEvolver` could never be decorated, as it's impossible to satisfy the cast. This leads to the Platform client having to destruct and re-construct the `EventSourcingRepository`, creating very close coupling for something that should be easy.

This PR splits the annotation-specific functionality, the providing of class information about handlers, to the `RepresentationResolvingEntityEvolver`. Now, the `EntityEvolver` can be decorated, as long as decorators implement `RepresentationResolvingEntityEvolver` as well. The module configuration is no longer bound to an implementation, but to an interface, removing the brittle re-construct logic for Platform
@CodeDrivenMitch CodeDrivenMitch self-assigned this Jul 20, 2026
@CodeDrivenMitch
CodeDrivenMitch requested a review from a team as a code owner July 20, 2026 17:26
@CodeDrivenMitch
CodeDrivenMitch requested review from hatzlj, laura-devriendt-lemon and zambrovski and removed request for a team July 20, 2026 17:26

@hatzlj hatzlj left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some small findings and one design question regarding the breaking change, but none is blocking in my opinion

return definition.createIdResolver(entityType, idType, representationResolvingEntityEvolver, c);
}
// Not possible, except when decorating, as this is a component configured by this module.
throw new IllegalArgumentException("The EntityMetamodel does not implement RepresentationResolvingEntityEvolver. Make sure that all decorators implement and delegate this interface.");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably switch to AxonConfigurationException,

Suggested change
throw new IllegalArgumentException("The EntityMetamodel does not implement RepresentationResolvingEntityEvolver. Make sure that all decorators implement and delegate this interface.");
throw new AxonConfigurationException("The EntityMetamodel does not implement RepresentationResolvingEntityEvolver. Make sure that all decorators implement and delegate this interface.");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good one, will adjust

* {@link QualifiedName}. This is useful for components that provide annotation-based usage of entities as it provides
* information on the wanted Java class (the representation) by the user.
*
* @param <E> The entity type this evolver applies to.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we decided to use fragment style consistently some time ago

Suggested change
* @param <E> The entity type this evolver applies to.
* @param <E> the entity type this evolver applies to

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wow that's a 180 form when I was in the Framework team! Will adjust

Comment on lines +37 to +40
* @param qualifiedName The {@link QualifiedName} of the handler to look for.
* @return The {@link Class} of the expected representation for handlers of the given {@code qualifiedName}, or
* {@code null} if no such representation is found.
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we decided to use fragment style some time ago

Suggested change
* @param qualifiedName The {@link QualifiedName} of the handler to look for.
* @return The {@link Class} of the expected representation for handlers of the given {@code qualifiedName}, or
* {@code null} if no such representation is found.
*/
* @param qualifiedName the {@link QualifiedName} of the handler to look for
* @return the {@link Class} of the expected representation for handlers of the given {@code qualifiedName}, or
* {@code null} if no such representation is found

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjusted

var component = (AnnotatedEntityMetamodel<E>) c.getComponent(EntityMetamodel.class, entityName());
return definition.createIdResolver(entityType, idType, component, c);
var component = c.getComponent(EntityMetamodel.class, entityName());
if(component instanceof RepresentationResolvingEntityEvolver representationResolvingEntityEvolver) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if(component instanceof RepresentationResolvingEntityEvolver representationResolvingEntityEvolver) {
if (component instanceof RepresentationResolvingEntityEvolver representationResolvingEntityEvolver) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjusted

EntityIdResolver<ID> delegate) {
this.idType = Objects.requireNonNull(idType, "The idType should not be null.");
this.metamodel = Objects.requireNonNull(metamodel, "The metamodel should not be null,");
this.entityEvolver = Objects.requireNonNull(entityEvolver, "The entityEvolver should not be null,");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
this.entityEvolver = Objects.requireNonNull(entityEvolver, "The entityEvolver should not be null,");
this.entityEvolver = Objects.requireNonNull(entityEvolver, "The entityEvolver should not be null.");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjusted

Configuration entityConfiguration =
entityModuleConfiguration(componentRegistry.build(lifecycleRegistry), COURSE_ENTITY_NAME);

// Sanity check that the decorator actually reached the entity's (nested) EntityMetamodel component —

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

those em-dashes :-)

Suggested change
// Sanity check that the decorator actually reached the entity's (nested) EntityMetamodel component
// Sanity check that the decorator actually reached the entity's (nested) EntityMetamodel component,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjusted

@hjohn

hjohn commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

I'm not fully convinced yet if we should do it like this :)

We're still accepting a plain EntityMetamodel on the messaging model builder, and IMHO, that means any EntityMetamodel should work (what's otherwise the point of interfaces). However at runtime this still fails now:

  • No indication during compile time that we did anything wrong
  • If later there is another component that needs another specific capability from the metamodel, then any decorator has to implement both...

I think we need to do either:

  • Somehow the builder should be honest and ask for the exact EntityMetamodel it wants, so there is no confusion and the API remains honest about what is needed. Whatever that is should be an interface so it can be decorated
  • Still consider just adding getExpectedReprentation to EntityMetamodel with a null default.

Any simple EntityMetamodel decorator the user now could add to the configuration would also break this; it has to also implement RepresentationResolvingEntityEvolver

@smcvb smcvb added Type: Enhancement Use to signal an issue enhances an already existing feature of the project. Priority 1: Must Highest priority. A release cannot be made if this issue isn’t resolved. labels Jul 21, 2026
@smcvb smcvb added this to the Release 5.3.0 milestone Jul 21, 2026
@smcvb

smcvb commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Closing this PR with the status obsolete, due to #4772 taking a different, slightly more optimized path instead.
Nonetheless, thanks for bringing this to our attention, @CodeDrivenMitch!
You can be assured that 5.3.0 will have a means to decorate the EntityMetamodel accordingly. :-)

@smcvb smcvb closed this Jul 23, 2026
@smcvb
smcvb deleted the enhancement/decoratable-entity-metamodel branch July 23, 2026 09:18
@smcvb smcvb added the Status: Obsolete Use to signal this issue is no longer necessary. label Jul 23, 2026
@smcvb smcvb removed this from the Release 5.3.0 milestone Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Priority 1: Must Highest priority. A release cannot be made if this issue isn’t resolved. Status: Obsolete Use to signal this issue is no longer necessary. Type: Enhancement Use to signal an issue enhances an already existing feature of the project.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants