'Domain and persistance layer, separation of concerns with repository

Domain layer

public interface IRepository<T> where T : MyEntity
{
}

public class Car : MyEntity
{
   ...   
}

public interface IMyCarRepository : IRepository<Car>
{
}

Persistance layer

public class Repository<T> : IRepository<T> where T : MyEntity
{
   public DbContext Context;
   public DbSet<T> DbSet;

   public Repository(DbContext db)
   {
      Context = context;
      DbSet = context.Set<T>();
   }
   ... Crud method implementation from the IRepository
}

public class CarRepository : Repository<Car>, ICarRepository
{
    private CarContext _db;
    public CarRepository(CarContext db) : base(context)
    {       
       _db = db;
    }
}

This works fine but as you can see CarRepository is using Car model from the Domain. Let's say that I want to change Car model in order to use MyEntityMongo I would change MyEntityMongo instead of MyEntity

public class MyEntityMongo
{
   [BsonId]
   public string Id {get; set;}
}

but this [BsonId] will bring MongoDB.Bson.Serialization.Attributes as a dependency into Domain which I don't want to do.

How would you do this without introducing Mongo library into a domain (or any other db related) The goal is to keep a clean separation of concerns between domain and persistance layer?



Solution 1:[1]

I'm not a C# dev by any means, but I would consider having persistence entities be distinct from domain entities, i.e. MyEntity is a persistence representation while some other type is the domain representation of the entity.

The repository is then essentially an adapter (or perhaps even an anti-corruption layer...) between the persistence infrastructure of loading and saving persistence representations to the database and the domain logic; as an adapter it logically depends on both.

Solution 2:[2]

You can implement repositories following Clean Architecture practices. In this sense, the repository interface does not belong to the repository or the Data Access Layer, but to the Business Logic. You should be able to design and code the Core of your application without implementing the real repositories until the end.

This has multiple implications in your example:

  1. Don't define a generic repository interface. As the interface belongs to the business logic, you will define repository interfaces for each entity. Not all entities will need the same methods in the repositories, so a generic interface is normally a bad idea.

  2. If you add a base class to your domain objects, do it because it makes sense in your domain object design, not because your persistence layer needs it.

  3. Your repository interfaces will get and pass domain objects, which is what the Core needs. Whatever the repository interfaces do with them, is irrelevant from the Core's point of view.

Once your Core is coded and unit tested, you can create the real implementations of the interfaces that you have defined while coding the Core. So, while implementing the repository for Car you evaluate 2 options:

  1. Entity Framework: EF allows you to map your Domain Objects directly to the DB with non-invasive ways (use IEntityTypeConfiguration). So, you can use your domain objects to define the DbSets in the DbContext and your repository implementation becomes quite simple with no extra mappings. In some cases, you might still have to map the domain object to a data object if you want or need a database structure very different from your domain object.

  2. MongoDb: Mongo really works well with very specific data models with mapping attributes. So, your mongo repositories will map the domain objects to Mongo objects back and forth.

Solution 3:[3]

In my view, it is better to have 2 different classes. One is for the domain logic and another one is for Persistence Model. Why? It is just my opinion, but because it is simpler and easier to refactor code in future.

In my opinion, this can be considered a highly subjective question.... My suggestion is based on DDD practices.

In the context of DDD, the repository layer should convert persistence entities to the correct business entities and return it.

Otherwise every service will have knowledge about persistence.

Some more explanations from Sapiens Works:

  • The Domain Model models real-life problems and solutions, it models BEHAVIOR.

  • The Persistence Model models what and how data is stored, it models STORAGE STRUCTURE. See? They have pretty different purposes. The domain is the reason the application exists and everything gravitates around it. The domain should not depend on anything,especially not on a persistence IMPLEMENTATION DETAIL like EF or NH. When you design the Domain Entities, they don't know anything about persistence. Persistence, database, doesn't exist.

and a little bit more:

When you design the Persistence Layer, that layer serves the Domain and depends on it. So the persistence needs to know about the domain but not vice-versa. You design the persistence entities for storage purposes and to match the ORM's constraints (like making all the properties virtual). So you'll have Domain Entities and Persistence Entities, each with their own different purposes and implementations.

Yes, they do resemble and sometimes can be identical (when the domain is very simple) but that's nothing more than a mere coincidence. Every time you're modeling something in a repository or using an ORM , you are modelling the persistence NOT the domain. There is a reason Eric Evans recommends to start the app with the domain and to IGNORE anything db related: the Domain should not be tainted with infrastructure details, because most of the people start everything with a database centric approach. And once you start with the db, everything will evolve around it and will be constrained by it. But you don't build the application for the database, you build it for the Domain, the database is just a Persistence implementation detail.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Levi Ramsey
Solution 2 Francesc Castells
Solution 3