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

Foi útil?

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

Licenciado em: CC-BY-SA com atribuição
scroll top