EntityNotFoundException behavior around @EntityCreator usage for Entity-centric and Stateful-Handler-based instance command handling#4609
Conversation
…utionException If the EntityCommandHandlingComponent is unable to find an entity-id when handling a command, we may be dealing with a creational command handler. Hence, we should invoke EntityMetamodel#handleCreate. If that's successful, our guess was correct. If this call returns a NoHandlerForCommandException, we were not dealing with a creational command handler at all. In that case, we should return the original EntityIdResolutionException #4362
When the EventStoreTransaction#appendEvent is invoked and we have tags without the existence of an AppendCondition, that signals a scenario where a creational command handler is invoked to append an event without any preceding source invocation. Thus, we should be able to assume we are dealing with the first event in this case. Making an ORIGIN marker with the found tags is restrictive, but should suffice in this case. #4362
Adjust tests to not have a resolvable identifier for creation. Although this means a shift, there's arguably more value in testing the suggested approach (to not have identifiers to load an entity when the command handler is static) then the other way around. This shift has shown the test cases to inconsistently set creational command handlers. Furthermore, the custom EntityIdResolver does not align. Lastly, the validation changes to an AppendEventsTransactionRejectedException for duplication, as the appendCondition is violated in these cases #4362
- Resolve nullability of getting a resource - Clarify updating a resource may mean you need to deal with null - Fix ugly indentation as a result of non-null/nullability settings - Clarify the ManagedEntity may not find an entity to load #4362
# Conflicts: # eventsourcing/src/main/java/org/axonframework/eventsourcing/eventstore/DefaultEventStoreTransaction.java
Changes to merge with axon-5.1.x #4362
Fix assetion to correclty validate the exception #4362
Use computeResourceIfAbsent #4362
Add the EntityMetamodel#CREATE_WITHOUT_LOAD resource key of type boolean. This resource should be set whenever the EntityCommandHandlingComponent decided to invoked EntityMetamodel#handleCreate **without** trying to load an entity first. This is important for the DefaultEventStoreTransaction to derive whether it should default the AppendCondition. This fine-tunes the support of the DefaultEventStoreTransaction that defaulted the AppendCondition for any set of tags, to only do this whenever a creational-command-handler is triggered. #4362
…Condition is defaulted Set EntityMetamodel#CREATE_WITHOUT_LOAD in test validating the AppendCondition is defaulted #4362
Co-authored-by: Jakob Hatzl <hatzlj@users.noreply.github.com>
…entstore/DefaultEventStoreTransaction.java Co-authored-by: Jakob Hatzl <hatzlj@users.noreply.github.com>
Drop redundant first() call #4362
Remove resource when we're failing #4362
# Conflicts: # modelling/src/main/java/org/axonframework/modelling/entity/EntityMetamodel.java # modelling/src/test/java/org/axonframework/modelling/entity/EntityCommandHandlingComponentTest.java
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.
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. |
This pull request is under discussion on purpose, as the team is not fully aligned yet on the direction.
What it provides, is a new test suite in the
axon-integrationtestmodule.The test includes 6 different entity+command handling+creation approaches:
For all 6 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.What's up to debate, and why this is still in under discussion, is whether we agree with the (1) test domains and (2) instance command handler outcomes per test.