[#4060] Align completeness between old Aggregate and new Entity description#4562
[#4060] Align completeness between old Aggregate and new Entity description#4562laura-devriendt-lemon wants to merge 11 commits into
Conversation
smcvb
left a comment
There was a problem hiding this comment.
Thanks for picking up this task, @laura-devriendt-lemon! I do have quite some comments to address, though. Arguable quite some I could've clarified on the original issue...so my apologies for that. So, please bare with me here! 🙏
Co-authored-by: Steven van Beelen <steven.vanbeelen@axoniq.io>
hatzlj
left a comment
There was a problem hiding this comment.
I have one concern regarding the handling of sealed types for polymorphic models and some minor improvement suggestions. Overall it reads great and easy to follow.
| - xref:commands:entities/event-sourced-entity.adoc[Event-Sourced Entities]: full reference for defining, configuring, and testing event-sourced entities in Axon Framework 5 | ||
| - xref:commands:entities/index.adoc[Entities]: overview of entity patterns (event-sourced, hierarchies, polymorphism) |
There was a problem hiding this comment.
i would flip the order for these two
|
|
||
| [TIP] | ||
| ==== | ||
| The command-centric approach aligns closest with [Vertical Slice-based Architecture](https://www.baeldung.com/java-vertical-slice-architecture). This style is useful when: |
There was a problem hiding this comment.
this refers to the enumeration of the different approaches for stateful commandhandlers ("command-centric approach" vs "entity-centric") that has been removed, would rephrase it a bit
| The command-centric approach aligns closest with [Vertical Slice-based Architecture](https://www.baeldung.com/java-vertical-slice-architecture). This style is useful when: | |
| Keeping commands in a separate component and receive the loaded entity as a parameter aligns closest with [Vertical Slice-based Architecture](https://www.baeldung.com/java-vertical-slice-architecture). This style is useful when: |
| @CommandHandler | ||
| public GiftCard(IssueCardCommand cmd, EventAppender eventAppender) { // <1> | ||
| // Validate command | ||
| public String handle(IssueCardCommand cmd, EventAppender eventAppender) { // <1> |
There was a problem hiding this comment.
as a creational handler this would rather be
| public String handle(IssueCardCommand cmd, EventAppender eventAppender) { // <1> | |
| public static String issue(IssueCardCommand cmd, EventAppender eventAppender) { // <1> |
| * Creating a resource with a client-supplied identifier, with no existing state to check. | ||
| * Forwarding a command to an external system that owns the state. | ||
|
|
||
| These are xref:commands:command-handlers.adoc[stateless command handlers]—simpler and cheaper to run. |
There was a problem hiding this comment.
you could directly reference the stateless command handlers heading by adding an anchor (for example [#stateless-command-handlers]) to it in command-handlers.adoc and referencing it here like
| These are xref:commands:command-handlers.adoc[stateless command handlers]—simpler and cheaper to run. | |
| These are xref:commands:command-handlers.adoc#stateless-command-handlers[stateless command handlers]—simpler and cheaper to run. |
|
|
||
| [TIP] | ||
| ==== | ||
| If you are unfamiliar with event sourcing, see xref:events:index.adoc[Events] for an introduction to the concept. |
There was a problem hiding this comment.
Either reference the Core concepts section on the events index page here like (requires to introduce a [#core-concepts] anchor at the heading there):
| If you are unfamiliar with event sourcing, see xref:events:index.adoc[Events] for an introduction to the concept. | |
| If you are unfamiliar with event sourcing, see xref:events:index.adoc#core-concepts[Events core concepts] for an introduction to the concept. |
Or alternatively directy link to the event sourcing page referenced there by changing it to:
| If you are unfamiliar with event sourcing, see xref:events:index.adoc[Events] for an introduction to the concept. | |
| If you are unfamiliar with event sourcing, see our explanation on link:https://www.axoniq.io/concepts/event-sourcing[**Event Sourcing**] for an introduction to the concept. |
| ---- | ||
| import org.axonframework.eventsourcing.annotation.EventTag; | ||
|
|
||
| public record CourseCreatedEvent( | ||
| @EventTag String courseId, // <1> | ||
| String title, | ||
| int capacity | ||
| ) {} | ||
| ---- | ||
|
|
There was a problem hiding this comment.
would be great to also include the entity with the tagKey set on the entity annotation in the example (similar as @MateuszNaKodach did in his Event tag migration guide PR
There was a problem hiding this comment.
Ah shortened entity, just showing that part, instead of a full-fledged solution. But otherwise, agreed with this point!
| EventSourcedEntityModule.declarative(String.class, CourseEntity.class) | ||
| .messagingModel((config, model) -> model | ||
| .creationalCommandHandler( // <1> | ||
| new QualifiedName(CreateCourseCommand.class), |
There was a problem hiding this comment.
since users tend to copy from examples, i would make the following the default as it convers both custom names/namespaces and the FQCN fallback (also below). Instead of the note below you can explain the reason for it in an additional callout:
| new QualifiedName(CreateCourseCommand.class), | |
| config.getComponent(MessageTypeResolver.class).resolveOrThrow(CreateCourseCommand.class).qualifiedName(), // <2> |
| === Autodetected | ||
|
|
||
| Use `@EventSourcedEntity(concreteTypes = {...})` and register with `EventSourcedEntityModule.autodetected()`. | ||
| Axon reads `concreteTypes` from the annotation and scans all subtypes automatically. |
There was a problem hiding this comment.
we can have an additional note here that for sealed types the subtypes are collected automatically without the need for specifying the concrete types (see org.axonframework.eventsourcing.configuration.AnnotatedEventSourcedEntityModule.getConcreteEntityTypes for how this is done), same applies for the spring boot configuration
There was a problem hiding this comment.
Loving this addition! It's a great feature that we have, so is good to show off!
|
|
||
| == Constraints | ||
|
|
||
| * Subtypes not listed in `concreteTypes` are silently ignored; their command and event handlers will not be registered and commands targeting them will fail at runtime. |
There was a problem hiding this comment.
not true for sealed types, they are detected automatically in the autodection/spring-boot case
smcvb
left a comment
There was a problem hiding this comment.
Sorry, bunch of comments to cover again. I think we need to do a bit more restructuring of the command-handlers.adoc and event-sourced-entity.adoc pages mostly.
|
|
||
| Axon supports two approaches to entity state management: | ||
| Axon supports **event-sourced entities**, where state is derived from a sequence of events. | ||
| State-stored entities are planned for a future release. |
There was a problem hiding this comment.
We do not have to mention if other things are planned for a future release here.
| State-stored entities are planned for a future release. |
| * Creating a resource with a client-supplied identifier, with no existing state to check. | ||
| * Forwarding a command to an external system that owns the state. | ||
|
|
||
| These are xref:commands:command-handlers.adoc[stateless command handlers]—simpler and cheaper to run. |
| = Entities | ||
| :navtitle: Entities | ||
|
|
||
| An entity maintains state across command invocations and uses that state to enforce business rules. |
There was a problem hiding this comment.
This sentence is true in the context of command handling, but that's not clearly stated in this sentence. Given that entity is a rather generic term, I'd prefer we make this sentence specific towards the fact we're talking about entities used for business validation / command handling. As not to overburden this notion for new people.
|
|
||
| [TIP] | ||
| ==== | ||
| If you are unfamiliar with event sourcing, see xref:events:index.adoc[Events] for an introduction to the concept. |
| == Entity identification | ||
|
|
||
| Every command that targets an entity instance must identify which instance to load. | ||
| Annotate the identifier field on the command with `@TargetEntityId`: |
There was a problem hiding this comment.
| Annotate the identifier field on the command with `@TargetEntityId`: | |
| To that end, we can annotate the identifier field on the command with `@TargetEntityId`: |
| ==== | ||
|
|
||
| [#entity-creation-with-entitycreator] | ||
| === Entity creator |
There was a problem hiding this comment.
Sorry, I think I wasn't clear in my earlier comment. I was thinking of making this a separate .adoc file instead of moving it in here! I mean, we should definitely reference it from this file, that's for sure. But, these three styles allow for different modes of constructing your entity. Modes that deserve space, on their own page.
Added, if we move this to it's own page, we can get to the most important part of this page, which is the basics of what an entity looks like.
But, let me know if you agree. If you don't we can discuss, of course!
| <1> Axon invokes this constructor with the first event when sourcing the entity. | ||
| Can be combined with an `@InjectEntityId`-annotated parameter if both the event and identifier are needed. | ||
|
|
||
| == Configuring an entity |
There was a problem hiding this comment.
Perhaps we can word and structure section slightly differently. Although technically it's about configuring, for the user it's about their entity.
So, if we start with a domain-centric Course entity class with everything in it, we can from their slowly show the configuration options.
E.g., we take the annotated example, but strip away all the annotations. Without the annotations, that class can be the input for a declarative configuration! In your example right now, you essentially do the assertion in side the (for example) instanceCommandHandler registration for the EnrollStudentCommand. But ideally, that registration invokes a method on the Course. It is that method which is the command handler. That is the layering Axon Framework provides around the Course just by having the annotations.
So, long story short, I think we could have:
- A show case of a bare
Courseentity. With command handlers, event sourcing handlers, state, and creation logic. Just no annotations. - Then that is followed up by explaining how we can configure that, taking the three options as drafted below.
- Firstly, the declarative configuration section would thus only show the
EventSourcingConfigurer#registerEntitystuff, not theCourseentity. Internally, it would refer to the methods of theCourseentity that have been shown earlier. - Second, we'd add the automated configuration through annotations. This would, of course, require a copy of the
Courseentity, but now with annotations. - Lastly, we'd do the Spring configuration version. For this one, I'd clarify we only need to switch the
@EventSourcedEntityannotation for@EventSourced.
Sorry for the long-winded explanation here... If you want to chat about it face-to-face before proceeding, let me know!
| import org.axonframework.messaging.eventstreaming.EventCriteria; | ||
| import org.axonframework.messaging.eventstreaming.Tag; | ||
|
|
||
| EventSourcingConfigurer configurer = EventSourcingConfigurer.create(); |
There was a problem hiding this comment.
I think it's better we move the configurer invocation logic inside a method, which is given the EventSourcingConfigurer as a parameter. Reasoning: I do not want to give the impression users need to create an EventSourcingConfigurer. Instead, they'll make the EventSourcingConfigurer once and pass it along from their public static void main. By ensuring our samples do not create an EventSourcingConfigurer every time, we make it so that people do not think that's the way to go.
There was a problem hiding this comment.
Furthermore, it would make for a compiling code sample, which this isn't as far as I know.
| === Autodetected | ||
|
|
||
| Use `@EventSourcedEntity(concreteTypes = {...})` and register with `EventSourcedEntityModule.autodetected()`. | ||
| Axon reads `concreteTypes` from the annotation and scans all subtypes automatically. |
There was a problem hiding this comment.
Loving this addition! It's a great feature that we have, so is good to show off!
|
|
||
| @CommandHandler | ||
| public void handle(RedeemCardCommand cmd, | ||
| @InjectEntity GiftCard card, // <3> |
There was a problem hiding this comment.
I'd prefer this example to be a separate chapter as well, actually 👼
It is the new way of handling commands and entities, allowing users to abstract out command handling logic from an entity object while still using annotations. Added, it is the way how to do Vertical Slice Architecture - to take a slice of your Event Model and recreate that in Axon Framework. That deserves it's own chapter entirely.
Granted, this is new, of course. We can either do that as part of this PR (which means you need to move the stateful command handler logic into a dedicated page) or in a follow-up PR.
Concluding, that would live the command-handlers.adoc devoid of entity-details, which I think is a good thing. For entity-details, we have different pages. That leaves the plain description of Command Handlers super straightforward and to the point, making it an easily digestible topic.
This PR resolves #4060.