it-swarm.com.de

Verstößt das Repository-Muster in einer sauberen Architektur nicht gegen das Prinzip der Abhängigkeitsinversion?

Nach dem, was ich gelesen und gesehen habe, geht eine saubere Architektur davon aus, dass Sie einige Entitäten haben, die nichts über Persistenz wissen, obwohl es auf derselben Ebene, auf der sie sich befinden, möglicherweise eine Schnittstelle gibt, die einen Vertrag darüber hat, wie die Entität gelesen/aktualisiert werden soll/gelöscht.

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

    public void SomeImportantBehavior {}
}

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

In der Zwischenzeit wird eine weitere Ebene die Implementierung der Schnittstelle enthalten:

// 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;
    }
}

Ich habe also eine Frage: Verstößt die Repository-Implementierung nicht gegen das DiP-Prinzip? Da es nicht nur auf die Abstraktion ankommt, sondern auch auf die konkrete Umsetzung (CarModel in diesem Fall)?

Ein weiteres Beispiel hierfür ist hier .

P.S. CarModel ist ein Domänenmodell mit Verhalten

5
arthur.borisow

Die zentrale Idee des DIP besteht darin, direkte Abhängigkeiten zwischen übergeordneten Modulen und untergeordneten Modulen zu beseitigen. Um dies zu erreichen, wird eine abstrakte, stabile Schnittstelle zwischen ihnen platziert, von der stattdessen beide Schichten abhängen.

Im Repository-Muster verweist die Schnittstelle normalerweise auf die Entität, für die das Repository verantwortlich ist. Daher hängt die Implementierung auf niedrigerer Ebene (CarModelRepository) von einer übergeordneten Modulentität (CarModel) ab.

Das Ersetzen der konkreten CarModel durch eine ICarModel Schnittstelle löst dieses Problem nicht wirklich. Schließlich implementiert die CarModel -Instanz, die wir über das Repository erhalten, verschiedene Geschäftsregeln und sollte daher in der Geschäftsschicht leben.

Wir könnten die Trennung zwischen der Business-Schicht und der Repository-Implementierung vergrößern. Zum Beispiel könnten wir ein CarModelFactory (in der Business-Schicht implementiert) bereitstellen, das ICarModelFactory (Teil der gemeinsam genutzten Schnittstelle) im Repository implementiert. Alternativ können wir das Repository dazu bringen, sich mit CarData -Wertobjekten und nicht mit tatsächlichen CarModel -Entitäten zu befassen - und die Geschäftsschicht veranlassen, die Entitäten selbst zu instanziieren.

In den meisten Fällen scheint dies nicht fruchtbar zu sein. Der Versuch, eine abstrakte Schnittstelle zwischen den beiden Modulen zu finden, die stabiler ist als CarModel selbst, wird fast immer vergeblich sein. Das liegt daran, dass CarModelRepository grundsätzlich keine generische Dienstprogrammklasse sein soll. Der Zweck besteht im Wesentlichen darin, Ihr spezifisches Domänenmodell an die Datenbank zu kleben.

Der Versuch, es vom Domänenmodell (oder der Datenbank) zu trennen, führt daher nicht wirklich zu etwas Nützlichem. Mit anderen Worten, das Repository kann als Adapter zwischen dem Domänenmodell und dem Datenbanktreiber angesehen werden - und muss daher beides kennen.

Ich sollte beachten, dass Martins Hauptaugenmerk bei der Einführung des DIP darauf lag, das übergeordnete Modul von den untergeordneten Modulen zu trennen, was durch das Repository-Muster erreicht wird.

5
doubleYou

Nein.

Betrachten Sie Ihren Code vor der Einführung des Repositorys:

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();
    }
}

und danach

public class MyView
{
    private IRepository repo;
    public void button_onClick()
    {
        this.Car = this.repo.GetCar(this.txt_id);
    }
}

Sie haben das DiP auf Ihre MyView-Klasse angewendet.

Sie können es weiterentwickeln und auch auf Ihr Repository anwenden. Vielleicht ist Auto eine abstrakte Klasse, wenn Sie mehr als einen Autotyp haben

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 jedem Fall entfernen wir eine gewisse Verantwortung von der betreffenden Klasse und fügen sie stattdessen als Abhängigkeit von einer Schnittstelle ein.

Anstelle dieser Klasse abhängig von einer bestimmten Implementierung einer untergeordneten Klasse, in diesem Fall der SQL-Logik. Die Low-Level-Klasse ist abhängig von der Schnittstelle, die gemäß den Anforderungen des High-Level-Objekts definiert wird.

Wenn Sie Car als Ihr übergeordnetes Objekt betrachten, können Sie das Prinzip auch hier anwenden. Offensichtlich macht es wenig Sinn, das Repository-Objekt zu injizieren. Aber sagen Sie zum Beispiel

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);
    }
}

Jetzt habe ich die Abhängigkeit von der Preisberechnungslogik umgekehrt

1
Ewan