Various topics from software architecture part 2: the Repository pattern
June 18, 2015 3 Comments
Introduction
In the previous post we looked at the RequestResponse messaging pattern. This pattern is often used in Service Oriented Architecture projects to simplify the communication between the service and its client.
In this post we’ll look at something very different. The Repository pattern is frequently employed in layered architectures where the domains in the Domain layer expose data access related operations through abstract interfaces. The actual repository layer, e.g. EntityFramework or MongoDb then implement those interfaces.
The primary goal of the Repository pattern is to decouple the domain objects from the physical repository layer. The domain objects and the domain layer are not supposed to know about the actual data access operations such as opening a database connection and querying a database. This is a common approach in Domain Driven Design (DDD).
We’ll go through the basic concepts of the pattern in this post. If you’d like to see a more complex design then you can go through the series on DDD. Also, there are multiple ways to implement the pattern but the main objective is to keep the technology-specific data access operations out of the domain layer.
A simple domain
Let’s start with a simple domain object which resides in the layer called Domains. The layer is a normal C# class library:
public class Customer : IDomain { public int Id { get; set; } public string Name { get; set; } public Address Address { get; set; } }
…where Address looks as follows:
public class Address { public string AddressLine1 { get; set; } public string AddressLine2 { get; set; } public string City { get; set; } public string PostalCode { get; set; } }
…and IDomain is just an empty interface to mark domain objects:
public interface IDomain { }
We’ll come back to this empty interface in part 5 of this series where we discuss the notion of aggregate root.
Persistence ignorance (PI)
As we said in the introduction we want to keep the domain layer independent of the actual repository layer, i.e. the one which is responsible for the data access related operations. Our goal is to make the concrete repository layer to depend on the domain layer and not vice versa which is often the case in real-life projects. This repository-independence is central in Domain Driven Design and has a special term for it: persistence ignorance.
The domain objects are kept clean by not polluting them with technology-specific data access operations. This implies that we shouldn’t have e.g. EntityFramework, NHibernate, MongoDb etc. specific code in the domain layer. As soon as we do that we’re implicitly creating a strong coupling between our domain objects and the concrete repository technology.
The result will be an independent domain layer that you can pass around without any tightly coupled dependencies. Also, you’ll be able to switch easily between concrete repositories, e.g. if you want to test MS SQL versus MySQL in the design phase or if your company wants to completely move the data layer to another technology, e.g. MongoDb.
Abstract repository
The repository pattern is used to declare the possible data access related actions you want to expose for your domain objects. The most basic of those are CRUD operations: insert, update, delete, select. You can have other methods such as insert many, select by ID, select by id and date etc., but the CRUD operations are probably the most common across all domains.
In order to keep the domain clean we only allow abstract repositories there. These are interfaces that declare the types of data-access operations that the concrete repository will be expected to implement.
We could first have the following generic repository interface that will be common across all your domain types. Note that this is located in the Domain layer and NOT in the concrete repository layer:
public interface IRepository<DomainType, IdType> where DomainType : IDomain { DomainType FindBy(IdType id); void Update(DomainType aggregate); void Insert(DomainType aggregate); void Delete(DomainType aggregate); }
Alternatively you can put this interface in a common infrastructure layer so that it can be reused throughout your DDD projects. Also, feel free to break up the interface into specialised ones, like IReadOnlyRepository or IDeletableRepository if you don’t want to enable these methods for all your domain objects, meaning that the above code is only an example, not some binding rule you have to follow or else terrible things can happen. In addition you can assign return values to those methods, like here:
public interface IRepository<DomainType, IdType> where DomainType : IDomain { DomainType FindBy(IdType id); bool Update(DomainType aggregate); IdType Insert(DomainType aggregate); bool Delete(DomainType aggregate); }
…where the boolean return type could indicate whether the operation was successful and the Insert method specifically returns the ID of the new object.
Next we can build a specialised customer repository interface which implements the above generic one. This is also where you can declare domain-specific data access operations. E.g. you might want to search for a customer by name:
public interface ICustomerRepository : IRepository<Customer, int> { Customer FindByName(string nameToSearch); }
Just to remind you we’re still within the Repository layer with no technology-specific data access operations.
The concrete repository
The concrete repository will be a separate layer in your project which references the domain layer. We can simply call it following the Repository.[Technology] pattern, e.g. Repository.EF, Repository.MongoDb etc. so that people will know which data access technology we use.
This is where you can feel free and implement the abstract repositories the way you want. Like above, there are no rules set in stone.
The most simplistic approach is to implement the domain-specific interfaces. Here’s an example with no implementation:
public class CustomerRepository : ICustomerRepository { public Customer FindByName(string nameToSearch) { throw new NotImplementedException(); } public Customer FindBy(int id) { throw new NotImplementedException(); } public void Update(Customer aggregate) { throw new NotImplementedException(); } public void Insert(Customer aggregate) { throw new NotImplementedException(); } public void Delete(Customer aggregate) { throw new NotImplementedException(); } }
If you’re building a concrete repository with EntityFramework you can then call upon the database context object to carry out the actual queries, or perform other database related operations on the Customer domain.
We’ll look at a more complex case in the next post where we take up another related concept, called the unit of work.
View the list of posts on Architecture and Patterns here.
Pingback: Architecture and patterns | Michael's Excerpts
Great explanation again Andras!. Thank you! I have a question, The technology for retrieve the data can be from a web service instead a database technology?
The actual repository implementation can be of any technology. It is up to you how you implement the various repository interfaces.