How to access 'real' aggregates within test?

Hi,
we are researching/learning Axon framework, and we stumbled upon one problem.

We have saga that orchestrates some glue logic between 2 aggregates. Event that triggers saga contains aggregate ids. In order to get some details needed for saga, we load aggregate instances using aggregate ids via event repository. It all works fine when application is started (within Spring container).
But, when we want to make test fixtures, we can’t inject event repository. At least I couldn’t find the way to do it.
If we mock repository, and we return stub aggregates for mocked repository load calls, it still does not work since ofc, state of stub aggregates does not get ‘affected’ by playing the events in fixture.
So, if we could ‘access’ somehow real aggregate instances behind fixture, we could connect them to mocked repository. But preferably, it would be great if it would work without need to mock repository.
Event repository that we use is just some handy proxy around org.axonframework.commandhandling.model.Repository, and it depends on @Autowired AxonConfiguration instance, that is also not available within test scope.

To illustrate, here is saga code:

@Saga
public class TenantApartmentApplicationSaga {

  @Inject
  private transient CommandGateway commandGateway;

  @Inject
  private transient EventRepositoryFactory factory;

  private transient EventRepository<Tenant> repoTenant;

  private transient EventRepository<Building> repoBuilding;

  private TenantId tenantId;

  @SagaEventHandler(associationProperty = TenantAppliedForApartmentEvent.APARTMENT_ID)
  @StartSaga
  public void on(TenantAppliedForApartmentEvent event) {
    tenantId = event.getTenantId();
    Tenant tenant = getRepoTenant().load(tenantId.getId());
    Building building = getRepoBuilding().load(event.getBuildingId().getId());
    Pair<Integer, Integer> apartmentData = building.getApartmentFloorAndNumber(event.getApartmentId());
    String messageText = String.format(
        "Tenant %s applies for apartment number %d of floor %d at building on address %s", tenant.getName(),
        apartmentData.getFirst(), apartmentData.getSecond(), building.getAddress());
    commandGateway.send(
        new StartConversationCommand(event.getTenantId(), Arrays.asList(building.getBuildingManagerId()), messageText),
        LoggingCallback.INSTANCE);
  }

  @SagaEventHandler(associationProperty = TenantApplicationForApartmentApprovedEvent.APARTMENT_ID)
  public void on(TenantApplicationForApartmentApprovedEvent event) {
    if (tenantId.equals(event.getTenantId())) {
      end();
    }
  }

  @SagaEventHandler(associationProperty = TenantApplicationForApartmentRefusedEvent.APARTMENT_ID)
  public void on(TenantApplicationForApartmentRefusedEvent event) {
    if (tenantId.equals(event.getTenantId())) {
      end();
    }
  }

  private EventRepository<Tenant> getRepoTenant() {
    if (repoTenant == null) {
      repoTenant = factory.createEventFactory(Tenant.class);
    }
    return repoTenant;
  }

  private EventRepository<Building> getRepoBuilding() {
    if (repoBuilding == null) {
      repoBuilding = factory.createEventFactory(Building.class);
    }
    return repoBuilding;
  }

Test that has problem looks like following.
Variant where we do not mock repositories / factories:

@RunWith(MockitoJUnitRunner.class)
public class TenantApartmentApplicationSagaTest {

  private FixtureConfiguration fixture;

  @Before
  public void setUp() {
    fixture = new SagaTestFixture<>(TenantApartmentApplicationSaga.class);
  }

  @Test
  public void testTenantApartmentApplicationSaga_whenTenantAppliedForApartment_expectStartConversationCommand() {
    BuildingId buildingId = create(BuildingId.class, UUID.randomUUID());
    Building building = BuildingMother.building(buildingId, 3, 21);
    Apartment apartment = ApartmentMother.apartment(3, 20, buildingId);
    Tenant tenant = UserMother.tenant();
    BuildingManager buildingManager = UserMother.buildingManager();

    ManagerRegisteredEvent managerRegisteredEvent = from(buildingManager);
    BuildingRegisteredEvent buildingRegisteredEvent = from(buildingId, buildingManager.getId(), building);
    TenantRegisteredEvent tenantRegisteredEvent = from(tenant);
    ApartmentAddedEvent newApartmentAddedEvent = ApartmentAddedEventFactory.from(apartment);
    TenantAppliedForApartmentEvent tenantAppliedForApartmentEvent = new TenantAppliedForApartmentEvent(
        buildingId, apartment.getId(), tenant.getId());

    fixture.givenAggregate(buildingManager.getId().getId())
           .published(managerRegisteredEvent)
           .andThenAggregate(building.getId().getId())
           .published(buildingRegisteredEvent)
           .andThenAggregate(tenant.getId().getId())
           .published(tenantRegisteredEvent)
           .andThenAggregate(building.getId().getId())
           .published(newApartmentAddedEvent)
           .whenAggregate(building.getId().getId())
           .publishes(tenantAppliedForApartmentEvent)
           .expectDispatchedCommandsMatching(
               exactSequenceOf(
                   StartConversationCommandMatcher.newInstance(tenant.getId())
               )
           ).expectActiveSagas(1);
  }
}

Fails with NPE:

Caused by: java.lang.NullPointerException
at eu.execom.buildingmanager.domain.building.sagas.TenantApartmentApplicationSaga.getRepoTenant(TenantApartmentApplicationSaga.java:70)

because it can’t inject automatically that EventRepositoryFactory instance, and field remains null.

However, other variant of test that registers injectable object as mocked factory (and repositories):

@RunWith(MockitoJUnitRunner.class)
public class TenantApartmentApplicationSagaTest {

  private FixtureConfiguration fixture;

  @Mock
  private EventRepositoryFactory factory;

  @Mock
  private EventRepository<Tenant> tenantRepo;

  @Mock
  private EventRepository<Building> buildingRepo;

  @Before
  public void setUp() {
    fixture = new SagaTestFixture<>(TenantApartmentApplicationSaga.class);
    fixture.registerResource(factory);
  }

  @Test
  public void testTenantApartmentApplicationSaga_whenTenantAppliedForApartment_expectStartConversationCommand() {
    BuildingId buildingId = create(BuildingId.class, UUID.randomUUID());
    Building building = BuildingMother.building(buildingId, 3, 21);
    Apartment apartment = ApartmentMother.apartment(3, 20, buildingId);
    Tenant tenant = UserMother.tenant();
    BuildingManager buildingManager = UserMother.buildingManager();

    ManagerRegisteredEvent managerRegisteredEvent = from(buildingManager);
    BuildingRegisteredEvent buildingRegisteredEvent = from(buildingId, buildingManager.getId(), building);
    TenantRegisteredEvent tenantRegisteredEvent = from(tenant);
    ApartmentAddedEvent newApartmentAddedEvent = ApartmentAddedEventFactory.from(apartment);
    TenantAppliedForApartmentEvent tenantAppliedForApartmentEvent = new TenantAppliedForApartmentEvent(
        buildingId, apartment.getId(), tenant.getId());
    when(factory.createEventFactory(Tenant.class)).thenReturn(tenantRepo);
    when(factory.createEventFactory(Building.class)).thenReturn(buildingRepo);
    when(tenantRepo.load(tenant.getId().getId())).thenReturn(tenant);
    when(buildingRepo.load(buildingId.getId())).thenReturn(building);

    fixture.givenAggregate(buildingManager.getId().getId())
           .published(managerRegisteredEvent)
           .andThenAggregate(building.getId().getId())
           .published(buildingRegisteredEvent)
           .andThenAggregate(tenant.getId().getId())
           .published(tenantRegisteredEvent)
           .andThenAggregate(building.getId().getId())
           .published(newApartmentAddedEvent)
           .whenAggregate(building.getId().getId())
           .publishes(tenantAppliedForApartmentEvent)
           .expectDispatchedCommandsMatching(
               exactSequenceOf(
                   StartConversationCommandMatcher.newInstance(tenant.getId())
               )
           ).expectActiveSagas(1);
  }
}

Fails with problem that building aggregate that what we return as mocked repository load method result, is not what is actually maintained by axon under the fixture hood when plays the specified events… Exception is clear (well it is not readable from posted parts of the code, but by playing newApartmentAddedEvent an Apartment entity should be stored into local List field within Building aggregate, and that actually does happen - but in real aggregate within fixture, that we can’t access from outside (test code).

Caused by: java.lang.IllegalArgumentException: Apartment not found for apartmentId 4809da9c-97dc-4782-ac95-861d22e78eb3 in building 1192bc0c-150c-462d-8701-a7a84a95e442
at eu.execom.buildingmanager.domain.building.Building.getApartmentFloorAndNumber(Building.java:311)
at eu.execom.buildingmanager.domain.building.sagas.TenantApartmentApplicationSaga.on(TenantApartmentApplicationSaga.java:45)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.axonframework.messaging.annotation.AnnotatedMessageHandlingMember.handle(AnnotatedMessageHandlingMember.java:127)

So, what is the proposed way to solve this? Is it possible to access somehow real aggregates under the fixture, or to somehow configure/initialize some instance of AxonConfiguration within test and wrap event repository around it, or something else? I hope it is not meant that within Saga we can’t access aggregates via repository and to force all the data to be exposed only via event fields. Or even worse, to be forced to maintain in test code also stub objects to act as event sourcing handlers were handling them - then it is like rewriting the logic within the test code.

Let me know if I should clarify any parts of my question, and I hope I just miss some well known practice - since that seems like some standard use scenario to me :wink:
Thanks for help,
Vladimir

Hi Vladimir,

I would definitely not recommend coupling of sagas and aggregates. It’s going to be easier in future to split them in different services if necessary. So, my solution to this problem would be to have events holding necessary data. If, for some reason, this is not possible, then we still have problem of testing. You can always provide ‘real’ aggregate repositories. Based on your example, I would guess that you are using JPA repositories and not event sourced ones.

Hope this help a little!

Cheers,
Milan.

Hi Vladimir, Milan,

If Milan his assumption is correct, that you’re using non-eventSourced aggregates, then you should stay tuned for this issue on GitHub.

It will introduce test functionality for non-eventSourced aggregates.

If you however are using the EventSourcingRepository for you Aggregates (which currently is the default in Axon), then I’d suggest not to try to access the fields inside the aggregate. The events really state what is happening with the aggregate, so asserting if the events are of the type and contents you’d expect when your aggregate is handling a command is sufficient testing of it.

That’s my two cents.

Cheers,
Steven

Hi Milan, Steven.
Thanks for answers.

To clarify for a start, repositories in my example are event sourced repositories, at least are proxies around them. I pasted some code later that shows that.

First philosophy part. :wink: If I understood you, best practice that is recommended by Axon is not to use event sourced repositories within sagas since using them would make coupling to aggregates within saga?
In order to get all the data needed to ‘calculate’ the logic within saga that requires data parts from different aggregates, we should probably post new commands that would result with events that hold the data pieces from each aggregate separately, and then we ‘fork’ all the ‘resulting events’ and get all needed data for saga from events. I think that data can’t be stored beforehand in events that triggered saga originally, since we might require different data from underlying aggregates driven by saga logic itself. This would ensure less coupling to aggregates, I agree, but would add quite some number of new commands/events for things that could usually be getters from aggregate classes. If event sourced repositories should not be used within sagas, what would actually be recommended place or kind of code where this repositories should be used then?
Since we just need functionality to load aggregate by given id, we did ‘shortcut’ of using event source repositories. I guess it would not make sense if we use indirectly JPA repositories from ‘read’ model, via queries fired via QueryGateway, that looks like even more complexity and coupling of query/read model to ddd/command model… I just look for what is best practice for such case - balance between less coupling and less complexity. You probably meant on JPA repositories in command model, no, we do not use that, still.

Now some technical parts. :wink:
I see that it was a bit unclear what kind of repository I was referring to, so here is the code that we used to define these repositories:

@Component
public class EventRepositoryFactory {

  private AxonConfiguration configuration;

  @Autowired
  public EventRepositoryFactory(AxonConfiguration configuration) {
    this.configuration = configuration;
  }

  public <T> EventRepository<T> createEventFactory(Class<T> aggregateClass) {
    return new EventRepository<>(this.configuration.repository(aggregateClass));
  }
}

(ok, I see that method would be better named as just create instead createEventFactory… ;))

and our EventRepository class:

public class EventRepository<T> {

  private final Repository<T> repository;

  public EventRepository(Repository<T> eventRepository) {
    this.repository = eventRepository;
  }

  /**
   * Tries to find aggregate for given Id -> if not found it returns null.

Hi Vladimir,

Quite some info you’re sharing here! Hope I’m dissecting it correctly, please tell me if I’m not.

From your response I get the feeling you’re using your Aggregates to query data from as well.
More specifically, you’re querying your Aggregates from within your Sagas, right?

First, that to me suggest you’re not strictly doing CQRS. The Aggregates are typically the Command Model within your application, thus they only perform ‘decision making based on internal state’.
Can I assume you’ve made a conscious decision not to follow the CQRS route for you application?

Now from your use case I notice you’ve wrapped Axon’s Repository implementations in your own EventRepository implementation to query information from the Aggregate.
If your Aggregate is however built up out of Events (which I assume it is, as you’re doing Event Sourcing), there should be plenty of events in your system telling exactly the same thing as your Aggregate does.

If you’re however not doing CQRS like I asked you previously, then querying an Aggregate isn’t a bad thing necessarily.

It should however still hold that you can deduce any state your Aggregate might be in based on the events, as you’re doing Event Sourcing.

The Saga it’s use case typically is to model a complex business transaction between several Aggregates (do note I’m saying several here, not all) within your application.

In an ideal world your Saga would be a stand alone unit which can work on those events alone for the state it needs to make its decisions.
This should also be possible, as the state of an aggregate follows from the stream of it’s events, events you can handle within a Saga instance.

Taking that assumption, you would thus never have to mock/wire one of Axon’s ‘Aggregate Repositories’ in your tests, as you can use the regular SagaTestFixtures function to provide all the givens to recreate the state of your Saga.

The AggregateTestFixture and SagaTestFixture are intentionally given to simplify the testing scenarios by only working with messages.

However, that’s more conceptually and maybe not entirely what you want.

Maybe you prefer to keep querying your Aggregates for their state which is, like I said earlier, completely fine if you don’t want or need to do CQRS.

Additionally, this is not going to answer your question directly. This is more just knowledge sharing (which I hope you find interesting).

The problem I think you have with your Saga and it’s test, is that you expect the SagaTestFixture to create your Aggregate instances as well.
I assume this because you’re stating given aggregates and published events which are not handled by your Saga at all.
In a SagaTestFixture, the published(Object) events should only contain events you actually handle in your Saga.
Thus you should be able to trim down your given aggregates/events in the test and let your own Repositories mock the Aggregates you’re querying.

Hope this helps you out Vladimir! Although I do feel your knowledge of the framework is pretty good, I’d still like to share the trainings we provide at AxonIQ. I’d think it great to discuss this in person with you, hence why I’m pointing it out.

Cheers,
Steven

Hi Steven.

Thanks for your patience to understand my questions and doubt.

I also consider this forum as knowledge sharing about DDD and CQRS, and ofc about Axon, great framework that helps a lot in implementing this principles, so there is no problem in telling me I’m wrong in anything, ofc :wink: Thanks for sharing training details, I’ll check with management our options to meet these, please do not feel obligated to answer all my questions if it goes beneath regular support that you give on this forum :wink:

If it is ok, I’ll try to clarify further what remains here to get on same page. :wink: I think that root of my problem is in understanding the ‘philosophy’ part, so I’ll focus more on this.

Well, yes, within Saga I had intention to query aggregate, but strictly on given aggregate ID -> so basically to call load on this aggregate event sourced repository. It was not my conscious decision not to follow the CQRS :wink:

Because, I had feeling that this was allowed to do within Saga -> and still not to violate CQRS principle? But ok, I see now from your answers that this is not something that should be practiced. But I still do not read clearly exact alternative that should be followed then.

To help putting that on clear path, I’ll try to put that my example shortest/clearest/concrete as possible as:

Lets assume we have 2 aggregates, an Building, and an Tenant. Tenant applies for apartment within Building, identified by some apartment number exposed via Building api, and apartment is entity within Building identified internally via some UUID like string Id. (Btw, we use typed ids, wrapped around generated UUID string ids).

When that apartment application event is applied, we would like to start Conversation (another aggregate) between Tenant and Building manager (yet another aggregate), that mentions building address and apartment details, as well as some Tenant details like name and title. For starting conversation we use saga, since that conversation lasts until application is eventually approved (or refused) by building manager. Now lets keep these other aggregates out of example here, to make it simpler ;). So, my point is, I need details from tenant and details from building/apartment, and I can’t have both in single event. And I need them to construct new command, within Saga. So, I’m on command side, and yes, I need some kind of query mechanism, I think? :wink:

From your previous answer I also read that since all the truth is already in events (since we do event sourcing), all the saga needs is to listen to already available events to construct all the data it needs. But, problem is that in this saga we start listening only upon specific event is applied (TenantAppliedForApartmentEvent), and therefore we couldn’t listen directly to already played events in past that were adding building address or other apartment details. Wouldn’t load query to event sourced repository of building for example, indirectly do that actually, for given building aggregated id? Maybe I miss some concept that helps with that?

As only alternative that I see, if I understood you, all these details that are needed to compose the conversation should be available within events? It is not enough to just raise event like this one:

@Value
public class TenantAppliedForApartmentEvent {

  public static final String APARTMENT_ID = "apartmentId";

  BuildingId buildingId;

  ApartmentId apartmentId;

  TenantId tenantId;
}

… and then ask for more details within saga (as I gave example in my first code snippet within first post in this question topic).

Instead,

  1. we should give more details within event itself (for example, since this event is being raised by Building aggregate, we should also put entire Building address and apartment details). Anyways, since we do not have enough details for Tenant, then we create new command within Saga (that looks like query), like:
    ObtainTenantDetails(tenantId)

  2. that command is handled by Tenant aggregate (that does not mutate tenant state, but just applies new event like:
    TenantDetailsObtained(tenantId, tenantName, tenantTitle, tenantEmail)

  3. that event is handled by our saga, that collects all needed details to compose needed conversation message, at that point.

That way we do not couple Saga to aggregate types, so saga is standalone, but we for sure add more coupling to events and commands that are created for purpose of this particular logic. And in future, lets say, we want to include into our message details like apartment energy label. That data is not in our events, since we can’t foresee everything upfront, ofc. So, at that point we would need one more command and event, or to modify some existing event -> meaning it would also change dependency of saga to these existing events that are already wrapped withing aggregates code.

Is that the way how it is intended to be used, but not to violate CQRS principles? Probably my aggregates need too much internal data of each other -> but we are just exploring the DDD and CQRS and Axon to learn it, on such pet project. Anyways, it seems to me that such scenario should happen a lot in some real systems.

I think it would help me a lot to understand this better if I get the answer where exactly it is ok to use load by Id method of event sourced repository, and not to break CQRS? On query side we can just use regular queries via QueryGateway, we do not need it there?

Hope that I succeed in making my question clear this time, and that you had patience to read it - hopefully with some answer for me :wink:

Cheers, Vladimir.

Hi Vladimir,

let me try to give a concise answer to address your question/concern. I think the problems/challenges you are facing are caused by a more fundamental one: you’re trying to model all interaction with the Saga using events. This is a good thing when it comes to triggering the Saga; this is exactly what events are for. But when it comes to the point where a Saga needs to know more than it could read based on events it received, this complicates things.

It’s important in these cases to take a step back, and wonder what it really is you want. In this case, the Saga needs information; it needs to know something. This literally translates to a query. Therefore, my recommendation is to implement this as a Query. Have the Saga read the tenant and building details from a query model that is designed to return that information efficiently.
This will also make your tests a lot simpler. Note that test complexity is generally a good indicator of the cleanliness of a design.

Hope this helps.

Allard

Hi Allard,

Thank you for your nice explanation. I also fell into trouble when I wanted to read some data for calculation inside Saga.
I ended up using Query model for the situation as you mentioned, but I was worried whether all the query model are updated from previous events when I want to execute next command. Isn’t there slight chance that there might be timing issue?

Thank you for your help!

Jihun

2018년 6월 8일 금요일 오후 4시 8분 38초 UTC+9, Allard Buijze 님의 말: