Why do i need a EventSourcingRepository for every aggregate?

I am trying to wrap my head around Axon with Spring Boot, Groovy, AMQP and MongoDb.

I am trying to recreate and enhance the TODOItem app and what i found out so far is that i need a EventSourcngRepository for every RootAgregate i have in my app.
Why is that? I have only one eventstore which every repository is referenced to, so why cant i have a default generic Repository that will handle all Aggregate creations.
As long as the aggregates have a noArgs constructor (which is required with event sourcing) this shgould not be a problem imo.

Could somebody clarify this for me?

My current config looks like this, maybe i can even strip it down:

@Configuration
@AnnotationDriven
public class AxonConfiguration {

    @Autowired
    Environment env

    @Bean
    FanoutExchange eventBusExchange() {
        new FanoutExchange(
                env.getRequiredProperty("spring.rabbitmq.axon.exchangeName"),
                env.getRequiredProperty("spring.rabbitmq.axon.exchangeDurable", Boolean),
                env.getRequiredProperty("spring.rabbitmq.axon.exchangeAutoDelete", Boolean)
        )
    }

    // Event bus queue
    @Bean
    public Queue eventBusQueue() {
        new Queue(
                env.getRequiredProperty("spring.rabbitmq.axon.queueName"),
                env.getRequiredProperty("spring.rabbitmq.axon.queueDurable", Boolean),
                env.getRequiredProperty("spring.rabbitmq.axon.queueExclusive", Boolean),
                env.getRequiredProperty("spring.rabbitmq.axon.queueAutoDelete", Boolean)
        )
    }

    @Bean
    public Binding binding() {
        BindingBuilder.bind(eventBusQueue()).to(eventBusExchange())
    }

    @Bean
    CommandBus commandBus() {
        new SimpleCommandBus()
    }

    @Bean
    public XStreamSerializer xstreamSerializer() {
        new XStreamSerializer()
    }

    @Bean
    public EventBusTerminal terminal(ConnectionFactory factory, XStreamSerializer serializer, ListenerContainerLifecycleManager lifecycleManager) {
        def terminal = new SpringAMQPTerminal()
        terminal.setConnectionFactory(factory)
        terminal.setSerializer(serializer)
        terminal.setExchangeName(env.getRequiredProperty("spring.rabbitmq.axon.exchangeName"))
        terminal.setListenerContainerLifecycleManager(lifecycleManager)
        terminal.setDurable(false)
        terminal.setTransactional(false)
        return terminal
    }

    @Bean
    AMQPConsumerConfiguration springAMQPConsumerConfiguration(ConnectionFactory factory) {
        def springAMQPConsumerConfiguration = new SpringAMQPConsumerConfiguration()
        springAMQPConsumerConfiguration.setDefaults(null)
        springAMQPConsumerConfiguration.setQueueName(env.getRequiredProperty("spring.rabbitmq.axon.queueName"))
        springAMQPConsumerConfiguration.setErrorHandler(TaskUtils.getDefaultErrorHandler(false))
        springAMQPConsumerConfiguration.setAcknowledgeMode(AcknowledgeMode.AUTO)
        springAMQPConsumerConfiguration.setConcurrentConsumers(1)
        springAMQPConsumerConfiguration.setRecoveryInterval(env.getRequiredProperty("spring.rabbitmq.axon.recoveryInterval", Long))
        springAMQPConsumerConfiguration.setExclusive(false)
        springAMQPConsumerConfiguration.setPrefetchCount(env.getRequiredProperty("spring.rabbitmq.axon.prefetchCount", Integer))
        springAMQPConsumerConfiguration.setTransactionManager(new RabbitTransactionManager(factory))
        springAMQPConsumerConfiguration.setTxSize(env.getRequiredProperty("spring.rabbitmq.axon.transactionSize", Integer))
        return springAMQPConsumerConfiguration
    }

    @Bean
    SimpleCluster simpleCluster() {
        new SimpleCluster(env.getRequiredProperty("spring.rabbitmq.axon.queueName"))
    }

    @Bean
    DefaultAMQPMessageConverter defaultAMQPMessageConverter(XStreamSerializer serializer) {
        new DefaultAMQPMessageConverter(serializer)
    }

    @Bean
    ListenerContainerLifecycleManager listenerContainerLifecycleManager(ConnectionFactory factory) {
        def listenerContainerLifecycleManager = new ListenerContainerLifecycleManager()
        listenerContainerLifecycleManager.setConnectionFactory(factory)
        return listenerContainerLifecycleManager
    }

    @Bean
    public CommandGateway commandGateway(CommandBus commandBus) {
        new DefaultCommandGateway(commandBus)
    }

    @Bean
    EventBus eventBus() {
        def bus = new SimpleEventBus()
        return bus
    }

    @Bean
    MongoTemplate axonMongoTemplate(MongoClient client) {
        new DefaultMongoTemplate(
                client
        )
    }

    @Bean
    EventStore eventStore(MongoTemplate template) {
        new MongoEventStore(
                template
        )
    }

    @Bean
    public EventSourcingRepository<ToDoItem> eventSourcingRepository(EventStore eventStore, EventBus eventBus) {

        EventSourcingRepository<ToDoItem> repository = new EventSourcingRepository(ToDoItem, eventStore);
        repository.setEventBus(eventBus);
        return repository;
    }
}

Hi,

a Repository is a DDD (Domain Driven Design) concept. It is an object that gives you references to (already existing) entities (or aggregates). So an ARepository will provide instances of A. A BRepository of B, etc. You’re completely free to decide how A and B are stored. You can choose to use Event Sourcing, Object-Relational storage, or a combination of the two. That choice is made per aggregate separately. Furthermore, the Repository will need to know what type of aggregate it has to construct from the event stream. Purely based on the events, Axon is not able to deduct it.

Hope that provides a clarification.
Cheers,

Allard

Hi Allard, in addition to your answer:

What is a common amount of aggregate roots in an application, lets say you have 100 aggregates, then you need to manually define 100 repositories for it in the configuration, even so they may share the same eventStore and could all easily created or fetched.(e.g. by class reference)
I understand that it is very flexible to decide for each aggregatge separately where an it is stored, but in the context of eventsourcing my understanding is that all events are stored in a collection (for mongodb) which you can define the eventstore.
This is my audit log, so when i use two event stores for my aggregates i am splitting up my auditlog.

Maybe i am still not getting the big picture, but in my app i would have hundreds of these:

Hi Nils,

you’re right that creating a (similar) configuration for each aggregate (100 is a lot!) is very cumbersome. In fact, we’re currently working on better Spring JavaConfig (if you’re still allowed to call it that way) for Axon. You’ll be able to add an @EnableAxonInfrastructure (or similarly named annotation) to one of your configuration classes, and Axon will automatically search for aggregates, sagas, etc to set up the required infrastructure for you. For most applications, that would reduce the amount of necessary configuration tremendously.

Cheers,

Allard

Hi Allard,

that sounds great, i already saw the issues posted about spring boot in the tracker.
Going the way to support Spring Boot will be a big step into the right direction.

Currently i am struggling with the correct wiring of all parts, but otherwise it looks like a great tool for my next project. Thanks for your work and effort.

Hi Allard,

Is such an annotation now ready ? I don’t see it in Axon 3 (or it may be present with a different name)

Cheers,
Abhi

Hi,

yes, this is available. In fact, the configuration is automatic when using Spring Boot. Just put the @Aggregate annotation on an aggregate, and it will configure the repository for you.
If you’re not on Spring Boot, you can use the Configuration API to register each aggregate (type). The Configuration API will automatically configure a repository for you.

Cheers,

Allard