Axon fixture injection fails in @CommandHandler annotated methods excepts for Constructor

I'm currently playing with Axon 4.2 and I have an aggregate (Customer) that use an injected service (CustomerService) in its @CommandHandlers methods.

A simplifed version of it (but still is valid for this example) is shown below.

@Aggregate
public class Customer {

  @AggregateIdentifier
  private Long id;
  private String name;
  private String address;

  @CommandHandler
  public Customer(CreateCommand command, CustomerService customerService) {
    log.debug( customerService.doSomething(command.getId()));
    AggregateLifecycle.apply(new CreatedEvent(command.getId(), command.getName()));
  }

  @CommandHandler
  public void on(UpdateCommand command, CustomerService customerService){
    log.debug( customerService.doSomething(command.getId()));
    AggregateLifecycle.apply( new UpdatedEvent(command.getId(),command.getAddress()));
  }

  @EventSourcingHandler
  public void on(CreatedEvent event){
    this.id = event.getId();
    this.name = event.getName();
  }

  @EventSourcingHandler
  public void on(UpdatedEvent event){
      this.address = event.getAddress();
  }
}

And this is the corresponding test:

@RunWith(MockitoJUnitRunner.class)
public class CustomerTest {

  @Mock
  private CustomerService customerService;
  private FixtureConfiguration<Customer> fixture;

  @Before
  public void setUp() {
    fixture = new AggregateTestFixture<>(Customer.class);
    fixture.registerInjectableResource(customerService);
  }

  @Test
  public void testCreation(){

    final Long id = 1L;
    final String name = "Elmo";
    when(customerService.doSomething(id)).thenReturn("called");

    fixture.givenNoPriorActivity()
            .when(new CreateCommand(id, name))
            .expectEvents(new CreatedEvent(id, name));

    verify(customerService).doSomething(id);
    verifyNoMoreInteractions(customerService);
  }

  @Test
  public void testUpdate(){

    final Long id = 1L;
    final String name = "Elmo";
    final String address = "Sesame street";

    when(customerService.doSomething(id)).thenReturn("called");

    fixture.givenState(() -> new Customer(id, name, null))
            .when(new UpdateCommand(id, address))
            .expectEvents(new UpdatedEvent(id, address));

    verify(customerService).doSomething(id);
    verifyNoMoreInteractions(customerService);
  }
}

the code just work fine but the there is a problem with the tests. In fact, the testCreation() test pass but the testUpdate() test fails with the following error.

org.axonframework.test.FixtureExecutionException: 
No resource of type [CustomerService] has been registered. It is required for one of the handlers being executed.

at org.axonframework.test.FixtureResourceParameterResolverFactory$FailingParameterResolver.resolveParameterValue(FixtureResourceParameterResolverFactory.java:58)
    at org.axonframework.messaging.annotation.AnnotatedMessageHandlingMember.resolveParameterValues(AnnotatedMessageHandlingMember.java:156)
    at org.axonframework.messaging.annotation.AnnotatedMessageHandlingMember.handle(AnnotatedMessageHandlingMember.java:132)
    at org.axonframework.messaging.annotation.WrappedMessageHandlingMember.handle(WrappedMessageHandlingMember.java:61)
    at org.axonframework.modelling.command.inspection.AnnotatedAggregate.handle(AnnotatedAggregate.java:427)
    at org.axonframework.modelling.command.inspection.AnnotatedAggregate.lambda$handle$3(AnnotatedAggregate.java:400)
    at org.axonframework.messaging.Scope.executeWithResult(Scope.java:111)
...

If I remove the CustomerService parameter (and the related code) in the on UpdateCommand method, then the testUpdate() test pass, so the problem seems to be in the dependecy injection.

Upvotes: 1

Views: 1171

Answers (4)

Solved. here

The problem was I set the fixgture like fixture.givenState(() -> new Customer(id, name, null)) and it should have been fixture.given(new CreatedEvent(id, name))

Upvotes: 0

First and foremost, thank you both for your responses.

@Ivan, I called it "CustomerService" in the example just as I could have called it "DomainService", "ComplexMathDomainCalculationUtils" or "DomainLogicExtractedToAnotherClassBecauseItWasTooBigAndComplexToBeThere" ;-) I just wanted to show an example of Dependecy Injection and for that I used a log.debug(), just to check the call to the injected resource.

As I mentioned, that code works when I run it. The "customerService" is injected via a SpringBeanParemeterResolver (I defined it as a spring bean)

@Steven, Yes, I've run them separateley and the result was the same: testCreation() pass and testUpdate() fails with the No resource of type [CustomerService] has been registered error message For both cases the CustomerService resources wasregistered into the Fixture via de @Before method.

I've uploaded the demo to Github

Upvotes: 0

Steven
Steven

Reputation: 7275

First and foremost, I would like to point out that Ivan Dugalic has a very good point. The Aggregate should be dealing with business logic instead of providing it to a service. You could potentially think of injecting a Domain Service, which in essence should be regarded as a state machine in that regard.

Other then the design question, the issue at hand is still weird. You have correctly defined a CustomerService mock and importantly registered it to the Fixture with the registerInjectableResource method.

Have you tried running the testCreation and testUpdate tests separately? And if so, do you still encounter the same exception? If the latter questions is also answered with yes, I would personally require to debug a little to figure out why the CustomerService is:

  1. Not registered at all
  2. Set to null, thus not 'injectable'
  3. Removed at any one location during the test cycle

Hope some of the above guides you to the right solution Ernesto!

Upvotes: 1

Ivan Dugalic
Ivan Dugalic

Reputation: 661

Aggregate is an important tactical pattern (prime building block) in DomainDrivenDesign (https://axoniq.io/resources/domain-driven-design). In Axon, Aggregates accept business commands, which usually results in producing an event related to the business domain – the Domain Event. You should not delegate your logic to some external CustomerService service at first place. You can find more details on how to design your Aggregate on Axon Reference guide. Additionally, this chapter provides an exhaustive list of all the possible parameters for annotated message handling functions.

Upvotes: 1

Related Questions