[#4362] Allow for commands without an Entity to succeed#4388
Open
smcvb wants to merge 4 commits intoaxon-5.1.xfrom
Open
[#4362] Allow for commands without an Entity to succeed#4388smcvb wants to merge 4 commits intoaxon-5.1.xfrom
smcvb wants to merge 4 commits intoaxon-5.1.xfrom
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
hatzlj
approved these changes
Apr 7, 2026
Contributor
hatzlj
left a comment
There was a problem hiding this comment.
looks good to me, no blockers
Comment on lines
-41
to
-43
| if (identifier != null) { | ||
| throw new IllegalStateException("Employee is an existing entity"); | ||
| } |
Contributor
There was a problem hiding this comment.
having switched to org.axonframework.modelling.entity.EntityMetamodelBuilder.creationalCommandHandler we don't need to check this anymore because the Metamodel makes sure a null entity is returned from the repository, right?
| if (!tags.isEmpty()) { | ||
| // No AppendCondition is present, but the event contains tags. | ||
| // Tags make no sense without an AppendCondition, so let's create an ORIGIN call | ||
| processingContext.updateResource(appendConditionKey, current -> current == null |
Contributor
There was a problem hiding this comment.
why not #computeResourceIfAbsent
Contributor
Author
|
The failing test tests something important: publishing two events during up front. Granted, it does this in our Test Fixtures. Never the less, I think we should have a test validating that a command handler that does not source an entity can also append several tagged events without issue, as this will be a common scenario. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request aims to resolve #4388, which is done in two steps.
Step 1 - Ensure the
EntityCommandHandlingComponentdeals withEntityIdResolutionExceptionThe original issue stems from the
EntityIdResolutionExceptionthrown by the automatically setEntityIdResolverthat looks for the@TargetEntityIdannoted field/value.However, commands creating an entity are not inclined to hold this identifier at all. An entity could very well invoke a service to create the identifier for the entity in question.
Furthermore, the predicament triggered for a
staticcommand handler INSIDE an entity. When thestaticcommand handler is placed in a regular component, the issue did not trigger (because theEntityIdResolverwasn't invoked.The combat this scenario, the
EntityCommandHandlingComponentnow has a try-catch around theEntityIdResolverinvocation.When an
EntityIdResolutionExceptionis thrown, theEntityCommandHandlingComponentwill "guess" that the command in question is a creational command handler.As such, it invokes the
EntityMetamodel#handleCreateright away.When the
EntityMetamodelinvocation fails with aNoHandlerForCommandException, we can be certain we were not dealing with a creational command handler.Hence, we fallback to throw the original
EntityIdResolutionExceptionfrom theEntityCommandHandlingComponent.However, when no
NoHandlerForCommandException, the guess was correct.Step 2 - Make an
AppendConditionwhen none is present onEventStoreTransaction#appendEventProviding the fix in step 1 clarified another predicament.
There currently is no means to consciously set the
AppendConditionwhen appending events.Axon Framework resolves this by constructing an
AppendConditionbased on anEventStoreTransaction#sourceinvocation.This works fine, if we have something to source.
In step 1, we effectively remove any sourcing invocation by forcefully taking the
EntityMetamodel#handleCreateroute.Hence, we need to have a fallback for this scenario.
Luckily, the
DefaultEventStoreTransactioninvokes the event-tagger lambda to constructTaggedEventMessageinstances out of the givenEventMessagetoEventStoreTransaction#appendEvent.If the
TaggedEventMessagecontainsTags, we can thus assume the user intends to append events that belong to a certain consistency boundary.A clear example are creational events that have been published as result of creational commands that do not have a
@TargetEntityId(or equivalent) annotated field/value.To comply to that scenario, the
DefaultEventStoreTransactionchecks if the currentProcessingContextcontains anAppendCondition.If so, it leaves the
AppendConditionas is, as it's been set as part of sourcing.When it is
nulland we haveTags, we can construct a origin-basedAppendConditionwith the given tags.We cannot assume any other consistency marker than origin, as the only means to get a concrete consistency marker is by sourcing.
Round-up
To test the above behavior, I have adjusted one of our integration tests by changing the creation commands to no longer hold an
@TargetEntityId.Although this means we do not test a create-or-update scenario with this command, I wagered the consequence to be neglibible. Especially given the other tests we have for create-if-missing.
Furthermore, I added some indenting and annotation changes to the touched files while working on this.
By doing the above, this PR resolves #4362