Use Repositories to Stay Focused

How DDD Repositories help to stay focused on your domain

Photo by Maksym Kaharlytskyi on Unsplash

I have already said it before, I am a huge fan of DDD.

One important concept in DDD is the idea of a repository. The purpose of a repository is to provide a kind of generic interface to a data store. In many cases, it is preferable* not to be too dependent on any one type of data storage system. By this I mean things like MySQL, PostgreSQL, MongoDB, Firebase, and so on. (There are LOTS of them.) Some of these are absolutely amazing technologies, and when adopted they ought to be adopted whole-heartedly. That said, when possible, for a number of reasons it is reassuring to avoid lock-in and use these as pluggable and therefore replaceable systems. That is where repositories come into play.

* Disclaimer: as with any design: “it depends”.

A repository is essentially an adapter layer over the data store. It creates a simple, but generalized view of the data store. Your system only needs to know how to interact with the repository; it does not need to know all the intricacies of each particular data store implementation. This presumes of course that your needs are generic, which is common, but may not always be the case. In theory it allows you to change one data store for another, or to use multiple data stores simultaneously without polluting your model or its implementation with technical constructs.

I implement my repositories as a simple CRUD interface (Create, Read, Update, Delete, though I use different words).

Here is the essence (with “advanced” features removed for clarity):

interface Repository<E> {
/**
* The name of the Repository.
*
* The name should be representative of what the data means
* within the model.
*/
name: string;
/**
* The schema version. The model will likely evolve over time. It
* can be possible to have multiples versions alive at any given
* time.
*/
version: Version;
/**
* Does the key exist in this Repository?
*
*
@param key the key to check for
*
@returns true if the key exists, false otherwise
*/
hasKey(key: string): Promise<boolean>;
/**
* Get a specific Entity from the Repository based on its key.
*
*
@param key the key of the Entity to retrieve.
*/
get(key: string): Promise<E>;
/**
* Create the entity in the Repository.
*
*
@param key the key of the Entity to create
*/
create(entity: E): Promise<Confirmation>;
/**
* Update an existing Entity with the provided state.
*
*
@param entity the state to use for updating the Entity
*/
update(entity: E): Promise<Confirmation>;
/**
* Delete the Entity from the Repository.
*
*
@param key the key of the Entity to delete
*/
delete(id: string): Promise<Confirmation>;
}

A Repository, although generic, remains a technical construct. It allows us to completely isolate the technicalities of the data store, but that is not the concept we want to expose in our domain model. When designing an aggregate root, we want to expose the aggregate in domain terms, not in technical terms. We want to think of the problem uniquely in terms of the domain, not in terms of repositories.

To illustrate, let’s take an overly simplistic example of maintaining the roster of a hockey team. In our case, we simply need to add newly drafted players to the roster, or remove retired players from the roster.

This is wrong!

const newPlayer1 = "Bob Smith";
const newPlayer2 = "Jim Jones";
const retiredPlayer = "Buck Rogers";
repository.create(newPlayer1);
repository.create(newPlayer2);
repository.delete(retiredPlayer);

This kind of pseudocode would work if implemented this way, but it goes completely against the principles of DDD. It is directly calling the repository, which is quite technical. Instead, we want something more like this:

interface Roster {
draftPlayer(player: Player): Promise<Confirmation>;
retirePlayer(player: Player): Promise<Confirmation>;
}

The Roster is the concept we would use. It only expresses our domain concepts (Player, Roster, draft, retire). In the background it uses the Repository, but all that technical jargon is hidden away nicely so we never need to speak in those terms. We should be able to speak naturally and normally, talking about drafting players rather than saving players to a database.

The wrong example above would be improved like this:

const bobSmith = new Player("Bob Smith");
const jimJones = new Player("Jim Jones");
const buckRogers = new Player("Buck Rogers");
roster.draftPlayer(bobSmith);
roster.draftPlayer(jimJones);
roster.retirePlayer(buckRogers);

It still “looks” technical, but the code could easily (and automatically) be rewritten and shown like this:

Draft player "Bob Smith" and add to the Roster.
Draft player "Jim Jones" and add to the Roster.
Retire player "Buck Rogers" and remove from the Roster.

The example is artificial and overly-simplistic, but I hope this conveys the basic meaning and purpose of a repository in DDD.

--

--

David Leangen | Entrepreneur & Software Engineer

Business-oriented engineer & technically oriented executive and entrepreneur. I apply technology to help small businesses thrive.