Reputation: 21
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
Reputation: 21
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
Reputation: 21
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
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:
null
, thus not 'injectable'Hope some of the above guides you to the right solution Ernesto!
Upvotes: 1
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