Clean Architecture – Does Repository Pattern Violate Dependency Inversion Principle?

clean codeclean-architecturedesign-patternsobject-orientedobject-oriented-design

From what I have read and seen clean architecture assumes that you have some entities which know nothing about persistence though at the same layer as they reside there might be an interface which has a purpose to be a contract on how the entity should be read/updated/deleted.

// project Core
public class CarModel
{
    public string Name { get; set; }

    public void SomeImportantBehavior {}
}

public interface ICarModelRepository 
{
    CarModel FindByName(string name);
    CarModel Add(CarModel carModel);
}

Meanwhile another layer will hold the implementation of the interface:

// project Infrastructure or Persistence
public class CarModelRepository : ICarModelRepository
{
    public CarModel FindByName(string name)
    {
        return new CarModel { Name = name }; // the implementation is not really important here
    }

    public CarModel Add(CarModel carModel) {
        // somehow persist it here
        return carModel;
    }
}

So I've got a question: doesn't repository implementation violate the DiP principle? Since it not only depends on abstraction but also on concrete implementation (CarModel in this case)?

Another example of this is here.

P.S. CarModel is a domain model with behavior

Best Answer

The central idea of the DIP is to eliminate any direct dependencies between higher-level modules and lower-level modules. To achieve this, an abstract, stable interface is placed between them, upon which both layers depend instead.

In the repository pattern, the interface usually references the entity for which the repository is responsible. It therefore makes the lower level implementation (CarModelRepository) depend on a higher-level module entity (CarModel).

Replacing the concrete CarModel with an ICarModel interface does not really solve this problem. After all, the CarModel instance that we obtain through the repository implements various business rules and should therefore live in the business layer.

We could increase the separation between the business layer and the repository implementation. For example, we could provide a CarModelFactory (implemented in the business layer) that implements ICarModelFactory (part of the shared interface) to the repository. Alternatively, we might make the repository deal with CarData value objects rather than actual CarModel entities - and make the business layer instantiate the entities itself.

In most cases, this doesn't seem fruitful. Attempting to find an abstract interface between the two modules that is more stable than CarModel itself is almost always going to be futile. That's because, fundamentally, CarModelRepository is not intended to be a generic utility class. It's purpose is essentially to glue your specific domain model to the database.

As a result, trying to separate it from the domain model (or the database) doesn't really produce anything useful. In other words, the repository can be seen as an adapter between the domain model and the database driver - and therefore must be aware of both.

I should note that Martin's primary focus when introducing the DIP was to separate the higher level module from lower level modules, which the repository pattern achieves.