DDD - Modelling the Domain - Repositories
The role of a Repository is to abstract away the database infrastructure by retrieving and persisting aggregates, their entities and value objects, giving the illusion of an in-memory data store (Evans, 2003). Typically, for each domain object that has a unique identity and needs persistence, we need to define a Repository in the Domain Model (Evans, 2003).
This repository is not a concrete implementation but rather an interface or an abstract class (Vernon, 2013) as shown in the code below:
from abc import ABCMeta
class ArticleRepository(metaclass=ABCMeta):
@abstractmethod
def next_identity(self):
"""Returns next unique identifier"""
@abstractmethod
def get(self, article_id):
"""Get an instance of an Article using its identifier"""
@abstractmethod
def find_by_title(self, keyword):
"""Finds all articles where keyword matches its title"""
@abstractmethod
def find_by_tag(self, tag):
"""Finds all articles matching the specified tag"""
@abstractmethod
def add(self, article):
"""Adds the article to the repository"""
@abstractmethod
def remove(self, article):
"""Removes the article from the repository"""
In the early stages of implementing a software system we could implement the above repository using an in memory data store e.g. using a simple dictionary as shown below:
class InMemoryArticleRepository(ArticleRepository):
def __init__(self):
self._articles = {} # an empty dictionary
def next_identity(self):
return str(uuid.uuid4())
def get(self, article_id):
return self._articles[article_id]
def find_by_title(self, keyword):
found = []
for article in self._articles.values():
if keyword in article.title:
found.append(article)
return found
def find_by_tag(self, tag):
found = []
for article in self._articles.values():
if tag in article.tags:
found.append(article)
return found
def add(self, article):
self._articles[article.id] = article
def remove(self, article):
self._articles.pop(article.id)
The implementation above provides fetching and saving articles into memory. Thus, any decisions on the database technology can be postponed for later and it helps us focus on the Domain Model without being distracted with ERD diagrams and the impedance mismatch between the domain objects and the relational schema (Nilsson, 2006). In addition, when writing unit tests we do not depend on the existence of a running database instance in order to retrieve an article and test its behaviour (Evans, 2003).
Vernon (2013) categorised the above example as a collection oriented repository i.e. it mimics a simple list of objects hiding completely any indication of a persistence data store. However, a concrete implementation depends on the ability of the database access framework to detect any domain object changes and persist them automatically (Vernon, 2013). If the chosen framework does not track object changes then we could implement the Unit of Work (UoW) design pattern ourselves, as described by Fowler (2002), although properly implementing UoW is a significant exercise. To overcome this limitation, Vernon (2013) identified the persistence oriented repository which is essential a collection oriented repository with the addition of a save method, that either inserts or updates records.
Although, we do not expect Domain Experts to describe their business in terms of Repositories and Factories, their role is very important in designing a complex software system (Evans, 2003).
References
- Evans, E. (2003) Domain-Driven Design: Tacking Complexity In the Heart of Software. Boston: Addison-Wesley.
- Fowler, M. (2002) Patterns of Enterprise Application Architecture. Boston: Addison-Wesley.
- Nilsson, J. (2006) Applying Domain-Driven Design and Patterns. Boston: Addison-Wesley.
- Vernon, V. (2013) Implementing Domain-Driven Design. Boston: Addison-Wesley.
This post is an excerpt of my MSc Thesis titled "Applying Domain-Driven Design in Python - designing a self-hosted Read-it-Later service" (January 2014).