Consolidate Entity discovery for Entity-centric and Stateful-Handler-centric - #4609
Conversation
My read on this issueWhat I think works really wellEntity-centric instance commands throwing The part I'm trying to understandFor stateful (
The no-arg and id-based handlers get a non-null entity even though the entity was never created, because those constructors don't need an event, so the framework can build one. In plain Java that is logical, but from a user's point of view it feels surprising: you ask for an entity that was never created and get back a real-looking instance instead of "nothing". The id-based case is the one I find most confusing. Because the id is already set, I couldn't find a reliable way for the handler to tell that the entity was never created The docs also present the entity-centric and stateful styles as basically interchangeable, so I wasn't sure users would expect them to diverge here. A direction I'd suggestTreat "no events yet means the entity doesn't exist" the same way for every creator style and every handler style:
That gives one existence rule everywhere, independent of which constructor the entity uses. Pros and cons of that approach
There may well be reasons this direction is not feasible that I'm not aware of, and I realize it would be a larger redesign that needs more analysis. I mainly wanted to share my initial thoughts early and hear how you see it. |
Add integration tests validating all entity-creator options for creational and instance command handler behavior. This means there are 12 tests against a sample domain (GiftCard), each using their unique domain implementation for testing purposes. As such there are 6 integrations of the GiftCard, being no-arg, id-based, and first-event-based entity creators, for (1) entity-centric and (2) stateful-command-handler-based command model handling
Introduce the EntityNotFoundException. This exception should be thrown whenever we are unable to find an entity. This essentially reflects the old AggregateNotFoundException from past framework installations. It should be thrown whenever a command handler cannot discover the entity to handle the command on correctly.
Adjust annotation-based EntityFactory to throw EntityNotFoundExceptions. By doing so, we ensure that the no-arg, id-based, and event-based @EntityCreator annotated field invocations fail with an EntityNotFoundExceptions when no previous event was found. Or differently put, if the entity's event stream didn't exist yet,
Catch EntityNotFoundException to return null. By doing so, an @InjectEntity annotated parameter may return null, giving the responsibility to the user to validate if the entity already exists for a given command handler.
26dfca5 to
0ccd516
Compare
Actually pass the firstEventMessage. That way, we are certain we are matching against the right parameter #4609
Check for nullability on @InjectEntity annotated parameters. This allows a user to define a nullable entity, to clarify the validation flow in the stateful command handler better #4609
Expand test suite to check for nullability provided for the @InjectEntity annotated parameters #4609
Provide documentation to explain nullability of the @InjectEntity annotated parameter #4609
EntityNotFoundException behavior around @EntityCreator usage for Entity-centric and Stateful-Handler-based instance command handling
MateuszNaKodach
left a comment
There was a problem hiding this comment.
Good job, but I (and my friend Klaudiusz) had a few finidings :)
…notation/reflection/AnnotationBasedEventSourcedEntityFactory.java Co-authored-by: Mateusz Nowak <mateusz@nakodach.pl>
Perform EntityNotFoundException validation first to not waste resources on checks that aren't needed otherwise #4609
Expand AnnotationUtils#hasAnnotationNamed to check the Parameter as well. This covers for the scenario where some (nullable) annotations are declarative only. #4609
Ensure the futures are checked within a given time frame. If we don't there's a chance we'll lose a failure or success #4609
…sted Ensure static factory methods for @EntityCreator are supported and tested. Only do this for identifier-factory creation to limit size. #4609
Mark EntityNotFoundException as non-transient. This was the case for the old Aggregate-based exception as well and aligns for this one too, so should be in place #4609
Use AxonConfigurationException consistently #4609
…ayload Only react on null event message i.o. ScannedEntityCreator#isWithoutPayload. Doing the latter may cause incorrect exceptions to user when they, for example, simply have a typo in the payload type. #4609
Add additional test coverage #4609
Add comment to explain why throw cannot be reached #4609
Catch EntityNotFoundException during load. This is required, as an EntityNotFoundException **may** mean that the user is actually waiting for a null or Optional (ManagedEntity) state object. If we don't do this, the EventSourcingRepository will not construct a ManagedEntity, which will thus not be updated for subsequent updates (read: appended events) performed by the stateful command handler using the @InjectEntity annotation in combination with @Nullable/Optional #4609
…exception # Conflicts: # examples/university-multi-tenancy-examples/university-multi-tenancy-core/src/main/java/org/axonframework/examples/demo/multitenancy/university/write/enrollstudent/EnrollStudentCommandHandler.java
Fix broken IT due to null support introduction #4609
hatzlj
left a comment
There was a problem hiding this comment.
I like the addition to support Optional and @Nullable, that makes a very clear API for handler methods. Only some nits and one comment/idea on the EventSourcedEntityFactory.create contract
MateuszNaKodach
left a comment
There was a problem hiding this comment.
Good job! :) I left one minor thing and replied your comments. I believe you will introduce the change with the exceptions :)
|
|
||
| Without either attribute set, Axon falls back to the default resolver, which reads the field marked with `@TargetEntityId` on the command payload (the form used in the example above). | ||
|
|
||
| [#_handling_a_missing_entity] |
There was a problem hiding this comment.
No, I agree - I believe it's a must, but we need to make it clear :)
| * @author Steven van Beelen | ||
| * @since 0.4.0 | ||
| */ | ||
| public class EntityNotFoundException extends RuntimeException { |
There was a problem hiding this comment.
Yes, I'd keep just EntityNotFoundException.
Enforce EventSourcedEntityFactory to return null for null event. This clarifies the contract usage down stream, allowing us to having a single point where the EntityNotFoundException is thrown, namely the InitializingEntityEvolver. This cleans the AnnotationBasedEventSourcedEntityFactory up by not having mixed concerns of sometimes returning an entity and sometimes throwing. Now it just returns something or nothing. #4609
Style and JavaDoc boyscouting #4609
Add test validating load-then-loadOrCreate correctly uses cached entity #4609
First and foremost, this pull request introduces a new test suite in the
axon-integrationtestmodule.The test includes 9 different entity+command handling+creation approaches:
For all 9 styles, we have two tests:
To ensure the above is true, I made some minor changes, which are:
EntityNotFoundException, akin to theAggregateNotFoundException. This can be thrown when applicable.AnnotationBasedEventSourcedEntityFactoryto throw anEntityNotFoundExceptionwhen there is no initial event, regardless of the entity-creator choice.InjectEntityParameterResolverto catch theEntityNotFoundExceptionto returnnullfor the entity when it couldn't be found.InjectEntityParameterResolverto check for the@Nullableannotation, as well as document this behavior.