Quote of the Day

more Quotes

Categories

Buy me a coffee

Repository pattern

Published January 10, 2021 in Architecture , Design Patterns - 0 Comments

The repository pattern is a common pattern for accessing data from a database and converting the data into an entity. The repository pattern is property the one I have used the most. But just recently, I started reading more on the different design patterns and realized a few things I did not know or misunderstood about the pattern.

Domain entity vs domain model

At some point, when starting a project from scratch, I was confused about the differences between entity and domain. I saw certain projects use entity whereas others use domain when naming the namespaces. I thought domain entity and domain model are interchangeable but that is not true. When learning or implementing the repository pattern, you may see these terms, and it would help to understand the differences between them.

An entity is a class that models closely the columns of a table in a database. An entity class always includes an identifier (id) property. The entity can also consists of other relationship properties that model the foreign keys between the table to another table in the database. For example, entity framework relies on the id and relationship properties to retrieve and map records in the table into instances of the entity class.

The Merriam Webster dictionary defines domain as:

a sphere of knowledge, influence, or activity

In that sense, examples of a domain can be: Entertainment, Healthcare, Education, Agricultural, Finance, Real estate, etc … As an example, if you are building an application for education, you may have domain classes such as Student, Subject, Classroom, Grade etc … These classes model the business concepts in the education domain and are domain models.

From my understanding, a domain model class and a domain entity class that represent a same real world object may have similar properties for the most part. However, a domain model class is intended for the presentation layer of an application, which does not need to concern with how the data is being persisted. As such, it may not have an id or other relationship properties. On the other hand, an entity class is intended for persistence and is often used by ORM tools to map data from a database into objects.

Aggregate and aggregate root

In his book “Domain Driven Design – Tackling Complexity in the Heart of Software”, Eric Evans defined an aggregate as:

A cluster of associated objects that we treat as a unit for the purpose of data changes

The author explains aggregate via an example of modeling a car in software for a repair shop. In the software, we may have an entity for the car, and also an entity for each of the parts for which there is an associated table in the database. For instance, besides a table for storing attributes of a car, we may have another table to store the attributes of a tire such as the manufacturer, available treadwear, type, size etc … The Car and the other entities that represent the parts form an aggregate.

The author also emphasizes important characteristics of an aggregate:

Each AGGREGATE  has a root and a boundary. The boundary defines what is inside the AGGREGATE . The root is a single, specific ENTITY contained in the AGGREGATE.

The concept of aggregate and aggregate root is important to understand when implementing the repository pattern. One of the mistakes I made is blindly creating a repository for each entity in an aggregate. However, I learned that for each aggregate, you should only have one repository which targets the root of the aggregate. That is because other objects outside of the aggregate do not interact directly with entities within the aggregate except the root. For instance, it does not make sense for a Tire or a Door entity to exist on its own, outside the boundary of the car entity.

Implementing the repository pattern

In an organized, multi-layered application , the responsibility of retrieving data from a database should reside at the infrastructure layer. A good way to encapsulate the data access logic at the infrastructure layer is using the repository pattern. A decent implementation should expose methods that allow callers to treat the repository of an entity as a collection type of that entity. Remember, you only need to create a repository for an aggregate root, not for every entity.

For example, in the application for a car repair shop, we may have the following entities within an aggregate which has an aggregate root.

   public interface IAggregateRoot<ID> 
   {
       public ID Id { get; set; }
   }
    
    public class Car : IAggregateRoot<int> 
    {
        public int Id {get; set;}
        // other properties of car 
    }
    
    public class Tire 
    {
        public int Id {get; set;}
        // other properties of tire
    }

One way to enforce in the program that only aggregate roots should have repositories is to use generic. We can have a generic type to flag an entity as an aggregate root and have a generic repository which accepts the type as argument.

public interface ICommonRepository<T, ID> where T : class, IAggregateRoot<ID>
    {
        T Find(ID id);
        IEnumerable<T> FindAll();
        T Create(T transientEntity);
        void Delete(ID id);
        T Update(T entity);
        IEnumerable<T> Find(Func<T, bool> expression);
    }

A concrete repository class is appropriate for writing any queries to get the desired data and mapping into entities.

 public class CarRepository: ICommonRepository < Car, int > 
 {
     IEnumerable < T > FindAll() 
     {
         IEnumerable < Car > cars = new List();
         try (Statement stmt = con.createStatement();) 
         {
             String SQL = "SELECT LastName, FirstName FROM Person.Contact ORDER BY LastName";
             ResultSet rs = stmt.executeQuery(SQL);
             while (rs.next()) 
             {
                 var car = new Car() {
                     Id = rs.getInt("Id");
                 }
                 ids.Add(car);
             }
         } 
         catch (SQLException e) 
         {
             e.printStackTrace();
         }

         return cars;
     }

     // other methods implementation ... 
 }

However, you may want to use an ORM framework instead of using pure JDBC driver classes and SQL queries. Two popular ORM frameworks for .NET is Dapper and Entity Framework. Dapper is a lightweight ORM tool that can automatically converts query results into entities. Entity Framework is a full flown framework that can only convert query results into entities but can also generate SQL queries based on configurations.

For instance, using entity framework, it’s fairly straightforward to implement the common CRUD operations in a base repository.

    public class CommonRepository<T, ID> : ICommonRepository<T, ID> where T : class,
        AggregateRoot<ID>
    {
        private readonly AppDbContext _dbContext;

        public CommonRepository(AppDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        public T Find(ID id)
        {
            return _dbContext.Set<T>().Find(id);
        }

        public IEnumerable<T> FindAll()
        {
            return _dbContext.Set<T>();
        }

        public T Create(T transientEntity)
        {
            var entityEntry = _dbContext.Set<T>().Add(transientEntity);
            _dbContext.SaveChanges();
            return entityEntry.Entity;
        }

        public void Delete(ID id)
        {
            _dbContext.Set<T>().Remove(Find(id));
        }

        public T Update(T entity)
        {
            var entityToUpdate = Find(entity.Id);
            // update other attributes
            var entityEntry = _dbContext.Set<T>().Update(entityToUpdate);
            _dbContext.SaveChanges();
            return entityEntry.Entity;
        }

        public IEnumerable<T> Find(Func<T, bool> expression)
        {
            return _dbContext.Set<T>().Where(expression);
        }
    }

Each repository of an aggregate root can extend from the base repository.

  public interface ICarRepository : ICommonRepository<Car, int>
    {
        public IEnumerable<Car> FindByOwnerName(string ownerName);
    }

    public class CarRepository : CommonRepository<Car, int>, ICarRepository
    {
        public CarRepository(AppDbContext dbContext) : base(dbContext) { }

        public IEnumerable<Car> FindByOwnerName(string ownerName)
        {
            throw new NotImplementedException();
        }
    }

References

Repository

Design the infrastructure persistence layer

Differentiating between domain, model, and entity with respect to MVC

Domain-Driven Design: Tackling Complexity in the Heart of Software

No comments yet