Doesn't repository pattern in clean architecture violate Dependency inversion principle?
https://softwareengineering.stackexchange.com/questions/388520
Pergunta
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
Solução
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.
Outras dicas
No.
Consider you code prior to the introduction of the repository:
public class MyView
{
public void button_onClick()
{
var sql = $"select form car where id = {this.txt_id}"
//do sql reading stuff
this.Car = new Car();
this.Car.Id = dr["id"].ToString();
}
}
and after
public class MyView
{
private IRepository repo;
public void button_onClick()
{
this.Car = this.repo.GetCar(this.txt_id);
}
}
You have applied the DiP to your MyView class.
You could take it further and apply it to your repository as well. Perhaps Car is an abstract class if you have more than one type of car
public CarRepo : IRepository
{
public ICarFactory carFac
public Car GetCar(string id)
{
//do sql stuff
var car = carFac.Create(dr["carType"].ToString(), dr)
return car
}
}
In each case we remove some responsibility from the class in question and instead inject it as a dependency on an Interface.
Instead of that class depending on a specific implementation of a lower level class, in this case the SQL logic. The low level class is dependent on the Interface, which is defined according to the needs of the High level object.
If you consider Car your high level object, you can also apply the principle here. Obviously theres little point injecting the repository object. But say for example
public class Car
{
private IPriceCalculator calc;
public decimal Price()
{
//comment out old code!
//return (new MyCalculator).CalcPrice(this.colour);
return this.calc.CalcPrice(this.colour);
}
}
Now I have inverted the dependency on the price calculation logic