Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AggregateTestFixture throws AggregateNotFoundException when a command handler with a creation policy applies no events #2242

Closed
mnegacz opened this issue Jun 2, 2022 · 2 comments
Assignees
Labels
Priority 1: Must Highest priority. A release cannot be made if this issue isn’t resolved. Status: Resolved Use to signal that work on this issue is done. Type: Bug Use to signal issues that describe a bug within the system.

Comments

@mnegacz
Copy link

mnegacz commented Jun 2, 2022

Basic information

  • Axon Framework version: 4.5.10
  • JDK version: OpenJDK Runtime Environment Temurin-11.0.14+9 (build 11.0.14+9)
  • Complete executable reproducer if available (e.g. GitHub Repo): Test snippet included below

Steps to reproduce

Create a test case for an aggregate with a command handler that uses the creation policy @CreationPolicy(AggregateCreationPolicy.CREATE_IF_MISSING). The command handler shouldn't apply any events.

Reproducer:

import org.axonframework.commandhandling.CommandHandler;
import org.axonframework.modelling.command.AggregateCreationPolicy;
import org.axonframework.modelling.command.AggregateIdentifier;
import org.axonframework.modelling.command.CreationPolicy;
import org.axonframework.modelling.command.TargetAggregateIdentifier;
import org.axonframework.spring.stereotype.Aggregate;
import org.axonframework.test.aggregate.AggregateTestFixture;
import org.junit.jupiter.api.*;

public class SomeAggregateTest {

    private final AggregateTestFixture<SomeAggregate> testee = new AggregateTestFixture<>(SomeAggregate.class);

    @Test
    void publishesNoEventsOnSomeCommand() {
        testee.givenNoPriorActivity()
              .when(new SomeCommand("id"))
              .expectNoEvents();
    }

    @Aggregate
    public static class SomeAggregate {

        @AggregateIdentifier
        private String id;

        public SomeAggregate() {
        }

        @CreationPolicy(AggregateCreationPolicy.CREATE_IF_MISSING)
        @CommandHandler
        public void handle(SomeCommand command) {
            // no events applied
        }
    }

    public static class SomeCommand {

        @TargetAggregateIdentifier
        private final String id;

        public SomeCommand(String id) {
            this.id = id;
        }

        public String getId() {
            return id;
        }
    }
}

Expected behaviour

The test case passes. It works on Axon Framework version 4.5.9

Actual behaviour

The exception is thrown:

11:37:38.021 [main] DEBUG org.axonframework.modelling.command.LockingRepository - Exception occurred while trying to load an aggregate. Releasing lock.
org.axonframework.modelling.command.AggregateNotFoundException: No 'given' events were configured for this aggregate, nor have any events been stored.
	at org.axonframework.test.aggregate.AggregateTestFixture$RecordingEventStore.readEvents(AggregateTestFixture.java:873)
	at org.axonframework.eventsourcing@4.5.10/org.axonframework.eventsourcing.EventSourcingRepository.readEvents(EventSourcingRepository.java:147)
	at org.axonframework.eventsourcing@4.5.10/org.axonframework.eventsourcing.EventSourcingRepository.doLoadWithLock(EventSourcingRepository.java:124)
	at org.axonframework.eventsourcing@4.5.10/org.axonframework.eventsourcing.EventSourcingRepository.doLoadWithLock(EventSourcingRepository.java:52)
	at org.axonframework.modelling@4.5.10/org.axonframework.modelling.command.LockingRepository.doLoad(LockingRepository.java:128)
	at org.axonframework.modelling@4.5.10/org.axonframework.modelling.command.LockingRepository.doLoad(LockingRepository.java:56)
	at org.axonframework.modelling@4.5.10/org.axonframework.modelling.command.AbstractRepository.lambda$load$5(AbstractRepository.java:131)
	at java.base/java.util.HashMap.computeIfAbsent(HashMap.java:1134)
	at org.axonframework.modelling@4.5.10/org.axonframework.modelling.command.AbstractRepository.load(AbstractRepository.java:130)
	at org.axonframework.modelling@4.5.10/org.axonframework.modelling.command.AbstractRepository.load(AbstractRepository.java:177)
	at org.axonframework.test.aggregate.AggregateTestFixture.detectIllegalStateChanges(AggregateTestFixture.java:550)
	at org.axonframework.test.aggregate.AggregateTestFixture.when(AggregateTestFixture.java:454)
	at org.axonframework.test.aggregate.AggregateTestFixture.when(AggregateTestFixture.java:437)
	at com.example.test/com.example.test.SomeAggregateTest.publishesNoEventsOnSomeCommand(SomeAggregateTest.java:19) <31 internal lines>
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) <9 internal lines>
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) <25 internal lines>
11:37:38.022 [main] DEBUG org.axonframework.messaging.unitofwork.AbstractUnitOfWork - Rolling back Unit Of Work.
11:37:38.022 [main] DEBUG org.axonframework.messaging.unitofwork.MessageProcessingContext - Notifying handlers for phase ROLLBACK
11:37:38.022 [main] DEBUG org.axonframework.messaging.unitofwork.MessageProcessingContext - Notifying handlers for phase CLEANUP
11:37:38.022 [main] DEBUG org.axonframework.messaging.unitofwork.MessageProcessingContext - Notifying handlers for phase CLOSED

org.axonframework.test.AxonAssertionError: The working aggregate was not considered deleted, but the Repository cannot recover the state of the aggregate, as it is considered deleted there.

	at com.example.test/com.example.test.SomeAggregateTest.publishesNoEventsOnSomeCommand(SomeAggregateTest.java:19) <31 internal lines>
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) <9 internal lines>
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1541) <25 internal lines>
@mnegacz mnegacz added Type: Bug Use to signal issues that describe a bug within the system. Status: Under Discussion Use to signal that the issue in question is being discussed. labels Jun 2, 2022
@smcvb smcvb self-assigned this Jun 2, 2022
@smcvb smcvb added Status: In Progress Use to signal this issue is actively worked on. Priority 1: Must Highest priority. A release cannot be made if this issue isn’t resolved. and removed Status: Under Discussion Use to signal that the issue in question is being discussed. labels Jun 2, 2022
@smcvb
Copy link
Member

smcvb commented Jun 2, 2022

This turns out to be a bug that sneaked into the framework, but for Command Handlers that construct Aggregates in general.
Writing something like the following:

class MyAggregate {
    @CommandHandler
    public MyAggregate(MyCommand command) {
        if (someStateFromTheCommand) {
            apply(new SomeEvent());
        }
    }
}

Where the if-statement isn't met now results in an IllegalArgumentException that the "Aggregate identifier may not be null" from the PessimisticLockFactory.
Chances are this got introduced in pull request #1854.
As such, it's a part of Axon Framework since release 4.5.3.

As seen from the sample, this predicament does not limit itself to the Creation Policy logic.
The AggregateTestFixture just deals with the Creation Policy logic differently, causes the predicament to show up as off 4.5.10.

@smcvb smcvb added this to the Release 4.5.11 milestone Jun 8, 2022
smcvb added a commit that referenced this issue Jun 8, 2022
During the commit phase of an Aggregate, the identifier is expected to
be present. Otherwise, no events or state-stored aggregate can be
inserted. However, if the identifier is not present at this point that
means the command handler decided not to create the aggregate. As such,
no commit action is necessary. Validate this behavior with a test case
targeted towards a command handling constructor without publishing any
events.

#2242
smcvb added a commit that referenced this issue Jun 8, 2022
If the AggregateNotFoundException is thrown within the test fixture and
the identifier is null, that means the identifier hasn't been set by the
 command handling constructor.

#2242
smcvb added a commit that referenced this issue Jun 8, 2022
If the PessimisticLockFactory throws a dedicated exception instead of an
 IllegalArgumentException than the AlwaysCreateAggregateCommandHandler
 can deal with this scenario by defining the result as null instead of
 rethrowing the exception. This covers for the scenario when an ALWAYS
 creation policy decides not to publish any events or sets state.

#2242
smcvb added a commit that referenced this issue Jun 8, 2022
Except new NullLockIdentifierException instead of the previous
IllegalArgumentException

#2242
smcvb added a commit that referenced this issue Jun 10, 2022
Instead of throwing a specific exception to handled in the case when the
 ALWAYS creation policy does not set the aggregateIdentifier, we'll
 instead construct a NoOpLock. This is safe and specific enough, since
 the AbstractRepository ensure we do not commit whenever the aggregate
 identifier is null.

#2242
smcvb added a commit that referenced this issue Jun 20, 2022
Adjust warning message to not be aggregate specific, as the LockFactory
is not aggregate specific either.

#2242
smcvb added a commit that referenced this issue Jun 20, 2022
- Test NoOpLock
- Test NullLockFactory
- Test new conditions in the LockingRepository around not obtaining a
lock

#2242
smcvb added a commit that referenced this issue Jun 20, 2022
Remove useless public modifier on test method

#2242
smcvb added a commit that referenced this issue Jun 20, 2022
[#2242] Correctly support null-identifier and no-event scenarios from Command Handling constructors, `Always`, and `Create-If-Missing` creation policies
@close-label close-label bot added the Status: Resolved Use to signal that work on this issue is done. label Jun 20, 2022
@smcvb smcvb removed the Status: In Progress Use to signal this issue is actively worked on. label Jun 20, 2022
@smcvb
Copy link
Member

smcvb commented Jun 20, 2022

I am closing this issue since pull request #2248 resolves it.

@smcvb smcvb closed this as completed Jun 20, 2022
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: Resolved Use to signal that work on this issue is done. Type: Bug Use to signal issues that describe a bug within the system.
Projects
None yet
Development

No branches or pull requests

2 participants