Getting started - finding and entry point

These are my first steps at using Axon. Intriguing. I get it. Now let’s make it work. So I created a spring based skeleton project using Initializr, and similar to the “getting started” I created two commands, two events, an aggregate, a projection, a rest controller, and unit tests. All good. However:

  • The unit test run but fail to actually instantiate the aggregate
  • The rest controller starts but a GET request does not trigger it
  • So I’ve writting a Spring @Component, but it is created before the handlers in the aggregate are registered.

How do I get an access point. Sight :smiley:

Hey Tom,

Nice that you started using AxonFramework. From the post here I cannot judge what is wrong with the implementation, maybe you can share the repository? Other than that we do have some code samples that may be of help. Or our hotel demo application. I hope to be able to get you up to speed soon!

Best regards,

Yvonne

Hey @tbee, good to see you here

As Yvonne said, it’s pretty hard to come up with responses without looking into the code!
Other than what she already shared, let me try to dive into some of the problems you mentioned with lots of assumptions from my side =)

  • Unit Tests
    Axon Framework provides something called AggregateTestFixture. This is what you should use for testing it!
    You can’t write ‘normal’ unit tests for it because your Aggregate has to be build from commands and events, that’s why this Fixture is there, to help! (There are other similar Fixtures to help with different components as well)

  • Not sure what you meant by Rest Controller starts but a GET does not trigger it

  • Here it is also not clear what is this @Component used for and where do you want to use it! Do you want to use a Component inside the Aggregate perhaps? If that is the case, you should inject the Component on the @CommandHandler annotated method! If that is not the case, I would need more context to help.

KR,

I moved my experiment code into a public bitbucket repo. It would be great if you to get me through these first hurdles :slight_smile: As said, it is nothing more then the getting started except using some other domain concepts.

https://bitbucket.org/sftwrks/swxselfroster

Hey Tom,

Looking at your code I found the following:

In the unit tests, event sourcing is also used to build up the state of the Aggregate. So instead of:

@Test
	void closeRosterBlock() {
		String rosterBlockId = UUID.randomUUID().toString();
		fixture.given(new CreateRosterBlock(rosterBlockId))
		  .when(new CloseRosterBlock(rosterBlockId))
		  .expectEvents(new RosterBlockClosed(rosterBlockId));
	}

you need to add the applied events in the given like this:

@Test
	void closeRosterBlock() {
		String rosterBlockId = UUID.randomUUID().toString();
		fixture.given(new RosterBlockCreated(rosterBlockId))
		  .when(new CloseRosterBlock(rosterBlockId))
		  .expectEvents(new RosterBlockClosed(rosterBlockId));
	}

The GET endpoint is commented out but looks good you just need to change it to the correct Query and expected payload:

    @GetMapping("/open")
    public CompletableFuture<List<RosterBlock>> findOpenRosterBlocks() {
        return queryGateway.query(new FindAllOrderedProductsQuery(), ResponseTypes.multipleInstancesOf(Order.class));
    }

This expects a QueryHandler in the projection class which looks like this:

	@QueryHandler
	public List<Order> getClosedRosterBlockIds(FindAllOrderedProductsQuery query) {
		//TODO: implement
	}

probably it did not work because the ActiveRosterBlocksProjection is not a Component and it could not find a QueryHandler annotated method for that query with the expected return type.

I hope this helps,

Yvonne

Of course! The givens need to be events and not commands. See, that is where the logic needs to sink in. Fixed. And thank you!

The projection in the getting started is also not a component, but that REST thing seems to be a tomcat problem.

Is there any way to prevent a Spring component to start before the Axon framework is initialized? @DependsOn or something? Because first the component is up.

!!!class nl.softworks.selfroster.Autostart.constructor commandGateway=org.axonframework.commandhandling.gateway.DefaultCommandGateway@3c74d846

Resulting in:

2022-02-18 11:53:23.995  WARN 19696 --- [ault-executor-0] o.a.c.gateway.DefaultCommandGateway      : Command 'nl.softworks.selfroster.CreateRosterBlock' resulted in org.axonframework.commandhandling.NoHandlerForCommandException(No Handler for command: nl.softworks.selfroster.CreateRosterBlock)

Then the handlers are registered:

2022-02-18 11:53:24.389  INFO 19696 --- [           main] i.a.a.c.command.impl.CommandChannelImpl  : Registered handler for command 'nl.softworks.selfroster.CloseRosterBlock' in context 'default'
2022-02-18 11:53:24.391  INFO 19696 --- [           main] i.a.a.c.command.impl.CommandChannelImpl  : Registered handler for command 'nl.softworks.selfroster.CreateRosterBlock' in context 'default'

(pushed the changes)

1 Like

What is the function of the AutoStart class? You can rely on the AutoConfiguration of Axon. I deleted the AutoStart class and it works.
I added a query:

public class GetOpenRosterBlockIds {

}

Changed the GET mapping:

    @GetMapping("/")
    public CompletableFuture<List<String>> findOpenRosterBlocks() {
        return queryGateway.query(new GetOpenRosterBlockIds(), ResponseTypes.multipleInstancesOf(String.class));
    }

And updated the queryHandler:

	@QueryHandler
	public List<String> getOpenRosterBlockIds(GetOpenRosterBlockIds query) {
		return openRosterBlockIds;
	}

Yvonne

The Autostart class is indeed a way around the fact that my REST service wasn’t accessible, so I wanted to bypass that. But the REST issue was caused by a missing trailing /. So I’m up and running! Great! Thanks for the help!

One more question: how is the support in Axon for Java record classes? Seems like the perfect candidate for commands and events. (Again; I get exceptions after converting them, so is that supported?)

I see you started a new thead Are java records supported?

I did, I figured it was such a change it topic it needed a separate thread.

That is fine, I added the link for others to find the answer :+1: