Demystifying repositories
Repositories are essential components in Axon Framework. However, they mostly remain behind the scenes, and developers don't need to interact with them directly. Usually, the framework can configure, instantiate and use the correct repository based on how the developer has constructed the domain components. That convenience sometimes leads to misunderstandings. Those are caused by the many meanings of the term "repository" and the assumptions developers make when they hear or read it.
Depending on what a given developer's background is, the term "repository" may be associated with
- 🟠 software repository (like Maven or npm)
- 🟠 code repository (like Git)
- 🟠 content repository (a database of digital content)
- 🟠 asset repository (a big "bucket" of assets)
- 🟠 data repository (a.k.a. data lake and data warehouse)
- 🟠 Spring Data Repository
- 🟠 DDD repository
There are likely more, but those are enough to illustrate the confusion. Even if the first few entries on the list above cross developers' minds, they should be able to reasonably quickly determine that none really makes sense in a framework like Axon. But then the "data repository" sounds generic enough. Those using Spring can easily assume the "Spring Data Repository" is the one. A "DDD repository" also sounds reasonable, given what the framework does. So which one Axon Framework refers to? Or did it add its own to the pile?
It's not about data lakes or data warehouses
Confusion may arise from overlaying abstract concerns over technical ones. Event Sourcing can be seen as a sort of implementation of a data lake storing indefinitely raw data (every single event in the system). Projections may appear as a sort of implementation of a data warehouse containing specific data for specific uses.
In such a line of thinking, both are data repositories, and thus people used to those concepts may expect that's what the term "repository" in Axon Framework is all about. It is not.
It's not about Spring Data Repository
Many developers use Axon Framework together with Spring and Spring Boot. Many falsely believe Axon Framework is built with Spring or requires Spring. While that demonstrates how well Axon fits in the Spring ecosystem, it also makes it easy to make false assumptions.
A Spring Data repository "significantly reduces the boilerplate code required to implement data access layers for various persistence stores". Axon needs to store data in various persistence stores. The conclusion that "repository" in Axon Framework refers to Spring Data Repository comes naturally to those who always use Spring to build applications. But that is not what it is. And yes, you don't have to use Spring if you don't want to.
It represents the term "repository" from Domain-Driven Design
The term originates from the "Domain-Driven Design: Tackling Complexity in the Heart of Software" book by Erik Evans. I highly recommend the book to any software developer.
Among other valuable concepts and patterns, DDD describes "repository" as follows:
"The Repository may store references to some of the objects. When an object is created, it may be saved in the repository, and retrieved from there to be used later. If the client requested an object from the repository, and the repository does not have it, it may get it from the storage. Either way, the repository acts as a storage place for globally accessible objects."
In a nutshell, it's an object repository. It is an abstract way to store and retrieve objects. The pattern does not specify how exactly this should be done. The Axon Framework's approach is somewhat stricter but generic enough and allows for multiple implementations:
"The repository is the mechanism that provides access to aggregates. The repository acts as a gateway to the actual storage mechanism used to persist the data. In CQRS, the repositories only need to be able to find aggregates based on their unique identifier. Any other types of queries should be performed against the query database."
So what does a "repository" actually do?
You can start with the Repository
interface to understand the concept in Axon Framework. That interface only provides methods to load and create new instances of an aggregate for a given identifier - the base functionality every repository must have, regardless of implementation.
The interface is implemented by the AbstractRepository
abstract class. This is where the essential logic of loading an aggregate within the current unit of work resides. It also takes care of dispatching the events associated with the aggregate when the unit of work is committed.
The LockingRepository
is another abstract class that extends the AbstractRepository
, providing a locking mechanism to prevent concurrent modifications of the aggregate. It is the base class for the two actual implementations of a repository that Axon Framework provides OOTB.
EventSourcingRepository
Both EventSourcingRepository
and CachingEventSourcingRepository
(providing improved loading performance) implementations rely on an Event Store
to persist aggregates (in the form of events).
- configuration:
- By default, the framework assumes you want an event-sourced aggregate (one with methods annotated with
@EventSourcingHandler
). Thus, unless you have explicitly configured it otherwise, it will create an instance ofCachingEventSourcingRepository
and configure it as the repository for the aggregate. - loading:
- When constructing an instance of an existing aggregate, the repository will instantiate an empty object and then trigger a process that reads all the past events related to the aggregate and calls the respective event-sourcing handler for each. It is also a function of the repository to optimize that process by taking into account snapshots and loading the intermediate aggregate state from the latest one.
- storing:
- The repository does not explicitly store aggregates. It does not have to - all related events (which will be used to load its state) are persisted in the
Event Store
when the unit of work is committed.
GenericJpaRepository
The GenericJpaRepository
implementation relies on data storage (typically a database) that can be accessed via JPA
.
- configuration:
- If your aggregate class is a valid
JPA
entity, the framework understands that you want a state-stored aggregate. Thus, unless you have explicitly configured it otherwise, it will create an instance ofGenericJpaRepository
and configure it as the repository for the aggregate. - loading:
- When constructing an instance of an existing aggregate, the repository will load the
JPA
entity from the configured data store. It will use the entity identifier to do so. That is why the same field must be both an aggregate identifier (@AggregateIdentifier
) and an entity identifier (@Id
). - storing:
- When the unit of work is committed, the repository will automatically store the updated
JPA
entity in the configured data store. -
Summary (TL;DR)
The term repository in Axon Framework strictly follows the DDD concept of the same name. It is an abstract mechanism that provides access to aggregates. It has nothing to do with Spring Data Repository or any other popular word meanings. It is both configurable and extendable, allowing multiple implementations to be used simultaneously for different aggregate types. By default, Axon Framework provides fully functional implementations for event-sourced and state-stored aggregates.