Wenn Sie gezwungen sind, ein anämisches Domänenmodell zu verwenden, wo setzen Sie Ihre Geschäftslogik und berechnete Felder ein?

StackOverflow https://stackoverflow.com/questions/1933351

Frage

Unser aktuelles O/RM -Tool erlaubt nicht wirklich reichhaltige Domänenmodelle, daher sind wir gezwungen, überall anämische Einheiten (DTO) zu nutzen. Dies hat gut funktioniert, aber ich kämpfe weiterhin damit, dass ich grundlegende objektbasierte Geschäftslogik und kalkulierte Felder einstellen kann.

Aktuelle Schichten:

  • Präsentation
  • Service
  • Repository
  • Daten/Entität

Unsere Repository -Ebene verfügt über den größten Teil der grundlegenden Abruf-/Validierungs-/Save -Logik, obwohl die Serviceschicht viele der komplexeren Validierung und Sparen durchführt (da Speichervorgänge auch Protokollierung, Überprüfung von Berechtigungen usw.). Das Problem ist, wo Sie einen solchen Code einstellen können:

Decimal CalculateTotal(LineItemEntity li)
{
  return li.Quantity * li.Price;
}

oder

Decimal CalculateOrderTotal(OrderEntity order)
{
  Decimal orderTotal = 0;
  foreach (LineItemEntity li in order.LineItems)
  {
    orderTotal += CalculateTotal(li);
  }
  return orderTotal;
}

Irgendwelche Gedanken?

War es hilfreich?

Lösung

Kommen wir zurück zu den Grundlagen:

Dienstleistungen

Dienste sind in 3 Geschmacksrichtungen erhältlich: Domänendienste, Anwendungsdienste, und Infrastrukturdienste

  • Domänendienste : Kapuliert die Geschäftslogik, die natürlich nicht in ein Domänenobjekt passt. In Ihrem Fall, alle Ihrer Geschäftslogik.
  • Anwendungsdienste : Wird von externen Verbrauchern verwendet, um mit Ihrem System zu sprechen
  • Infrastrukturdienste : Wird verwendet, um technische Bedenken abstrahieren (z. B. MSMQ, E -Mail -Anbieter usw.)

Repository

Hier ist Ihr Datenzugriff und Konsistenzprüfungen gehen. In reinem DDD, Ihre Aggregierte Wurzeln wäre verantwortlich für die Überprüfung der Konsistenz (bevor sie eine Objekte bestehen). In Ihrem Fall würden Sie Schecks von Ihrem verwenden Domänendienste Schicht.


Vorgeschlagene Lösung: Teilen Sie Ihre vorhandenen Dienste auseinander

Verwenden Sie eine neue Domänendienste Schichten Sie so, dass Sie alle Logik für Ihre DTOs und Ihre Konsistenz überprüft (mithilfe Ihrer Konsistenz Spezifikationen, kann sein?).

Verwenden Sie das Anwendungsdienst um die erforderlichen Fetch -Methoden freizulegen (FetchOpenOrdersWithLines), die die Anfragen an Ihre weiterleiten Repository (und verwenden Sie Generika, wie Jeremy vorschlug). Sie können auch in Betracht ziehen, zu verwenden Abfragetechnik um Ihre Fragen zu wickeln.

Von deiner Repository, verwenden Spezifikationen in deiner Domänendienste Ebene, um die Objektkonsistenz usw. zu überprüfen, bevor Sie Ihre Objekte bestehen.

Sie können unterstützende Informationen in Evans 'Buch finden:

  • "Dienste und die isolierte Domänenschicht" (S. 106)
  • "Spezifikationen" (S. 224)
  • "Abfragespezifikationen" (S. 229)

Andere Tipps

Ich bin versucht zu antworten Mu, aber ich möchte näher erläutern. Zusammenfassend: Lassen Sie nicht zu, dass Ihre Wahl von Orm vorschreibt, wie Sie Ihr Domänenmodell definieren.

Der Zweck des Domänenmodells ist es, eine reichhaltige objektorientierte API zu sein, die die Domäne modelliert. Wahr folgen Domänengetriebenes Design, Das Domänenmodell muss definiert werden durch Technologie nicht eingeschränkt.

Mit anderen Worten, die Das Domänenmodell steht an erster Stelle, und alle technologiespezifischen Implementierungen werden anschließend von angesprochen von Mapper Diese Karte zwischen dem Domänenmodell und der betreffenden Technologie. Dies beinhaltet häufig beide Möglichkeiten: zur Datenzugriffsschicht, in der die Auswahl der ORM Einschränkungen einführen kann, und in die UI -Schicht, in der die UI -Technologie zusätzliche Anforderungen erfüllt.

Wenn die Implementierung außerordentlich weit vom Domänenmodell entfernt ist, sprechen wir über eine Antikorruptionsschicht.

In Ihrem Fall ist das, was Sie ein anämisches Domänenmodell nennen, tatsächlich die Datenzugriffsschicht. Ihr beste Rückgriff wäre es zu definieren Repositorys Dieser Modellzugriff auf Ihre Entitäten auf technologisch neutrale Weise.

Schauen wir uns beispielsweise Ihre Bestelleinheit an. Die Modellierung einer von Technologie nicht eingeschränkten Bestellung könnte uns zu so etwas führen:

public class Order
{
    // constructors and properties

    public decimal CalculateTotal()
    {
        return (from li in this.LineItems
                select li.CalculateTotal()).Sum();
    }
}

Beachten Sie, dass dies ein einfaches altes CLR -Objekt ( Poco ) und ist somit durch Technologie nicht eingeschränkt. Nun ist die Frage, wie Sie dies in und aus Ihrem Datenspeicher in und aus herausbringen.

Dies sollte über einen abstrakten iOderrepository erfolgen:

public interface IOrderRepository
{
    Order SelectSingle(int id);

    void Insert(Order order);

    void Update(Order order);

    void Delete(int id);

    // more, specialized methods can go here if need be
}

Sie können jetzt iOderRepository mit Ihrem ORM Ihrer Wahl implementieren. Einige Ormen (z. B. das Entity -Framework von Microsoft) müssen jedoch die Datenklassen von bestimmten Basisklassen ableiten, sodass dies überhaupt nicht mit Domänenobjekten als Pocos passt. Daher ist ein Mapping erforderlich.

Das Wichtigste zu erkennen ist, dass Sie möglicherweise Datenklassen stark tippt haben, die semantisch ähneln Ihren Domäneneinheiten. Dies ist jedoch ein reine Implementierungsdetail. Lassen Sie sich daher nicht damit verwirren. Eine Bestellklasse, die sich aus dem EG ergibt EntityObject ist keine Domänenklasse - Es handelt sich um ein Implementierungsdetail. Wenn Sie also IorderRepository implementieren, müssen Sie die Reihenfolge abbilden Datenklasse zur Bestellung Doman -Klasse.

Dies mag mühsame Arbeit sein, aber Sie können verwenden Automapper Um es für dich zu tun.

Hier erfahren Sie, wie eine Implementierung der Auswahlmethode aussehen könnte:

public Order SelectSinge(int id)
{
    var oe = (from o in this.objectContext.Orders
              where o.Id == id
              select o).First();
    return this.mapper.Map<OrderEntity, Order>(oe);
}

Genau dafür ist die Serviceschicht - ich habe auch Anwendungen gesehen, in denen sie als Businesslogic -Schicht bezeichnet wird.

Dies sind die Routinen, die Sie den größten Teil Ihrer Zeit testen möchten, und wenn sie in ihrer eigenen Ebene sind, sollte die Repository -Ebene unkompliziert sein.

Die Repository -Ebene sollte so weit wie möglich generiert werden, daher ist sie kein geeigneter Ort für die Geschäftslogik, die für bestimmte Klassen individuell ist.

Nach dem, was Sie sagen, kann es sein, dass Sie zu starr über Ihren Service und Ihre Repository -Ebenen denken. Es hört sich so an, als ob Sie nicht möchten, dass Ihre Präsentationsebene eine direkte Abhängigkeit von der Repository-Ebene hat. Um dies zu erreichen, doppelten Sie Methoden Ihrer Repositorys (Ihre Pass-Through-Methoden) in der Serviceschicht.

Ich würde das in Frage stellen. Sie können das entspannen und zulassen, dass beide in Ihrer Präsentationsschicht verwendet werden und Ihr Leben für den Start einfacher werden. Vielleicht fragen Sie sich, was Sie erreichen, indem Sie die Repositorys so verbergen. Sie haben bereits Persistenz abstrahiert und die Implementierung mit ihnen abfragt. Das ist großartig und wofür sie konzipiert sind. Es scheint, als ob Sie versuchen, eine Serviceschicht zu erstellen, die die Tatsache verbirgt, dass Ihre Entitäten überhaupt bestehen bleiben. Ich würde fragen, warum?

Die Berechnung der Bestellungsummen usw. Ihre Serviceschicht wäre das natürliche Zuhause. Eine SalesorderCalculator -Klasse mit Linetotal (LineItem LineItem) und Ordertotal (Order Order) -Methoden wäre in Ordnung. Möglicherweise möchten Sie auch in Betracht ziehen, eine angemessene Fabrik -EG -Bestandteile zu erstellen. CreateorderCalculator (), um die Implementierung bei Bedarf zu wechseln (Steuer auf Bestellrabatt enthält länderspezifische Regeln). Dies könnte auch einen einzigen Einstiegspunkt für die Bestellung von Diensten bilden und das Finden von Dingen durch Intellisense erleichtern.

Wenn dies all dies nicht verarbeitet klingt, müssen Sie tiefer darüber nachdenken, was Ihre Abstraktionen erreichen, wie sie sich miteinander beziehen und die Einzelverantwortungsprinzip. Ein Repository ist eine Infrastrukturabstraktion (versteckt, wie Entitäten gerettet und abgerufen werden). Services sind abstrahiert, um die Implementierung von Geschäftsaktionen oder -regeln abstrahieren und eine bessere Struktur für die Versionierung oder Varianz ermöglichen. Sie sind im Allgemeinen nicht so geschichtet, wie Sie beschreiben. Wenn Sie komplexe Sicherheitsregeln in Ihren Diensten haben, sind Ihre Repositorys möglicherweise das bessere Zuhause. In einem typischen Modell des DDD -Stils würden Repositories, Entitäten, Wertobjekte und Dienste nebeneinander in derselben Ebene und als Teil desselben Modells verwendet. Die obigen Schichten (typischerweise Präsentation) würden daher durch diese Abstraktionen isoliert. Innerhalb des Modells kann die Implementierung eines Dienstes die Abstraktion eines anderen verwenden. Eine weitere Verfeinerung fügt Regeln hinzu, die Referenzen auf welche Entitäten oder Wert Objekte enthalten, die einen formelleren Lebenszykluskontext durchsetzen. Für weitere Informationen würde ich empfehlen, die zu untersuchen Eric Evans Buch oder Domain getriebenes Design schnell.

Wenn Ihre ORM -Technologie DTO -Objekte nur gut behandelt, heißt das nicht, dass Sie reichhaltige Entitätsobjekte wegwerfen müssen. Sie können Ihre DTO -Objekte immer noch mit Entitätsobjekten einwickeln:

public class MonkeyData
{
   public string Name { get; set; }
   public List<Food> FavoriteFood { get; set; }
}

public interface IMonkeyRepository
{
   Monkey GetMonkey(string name) // fetches DTO, uses entity constructor
   void SaveMonkey(Monkey monkey) // uses entity GetData(), stores DTO
}


public class Monkey
{
   private readonly MonkeyData monkeyData;

   public Monkey(MonkeyData monkeyData)
   {
      this.monkeyData = monkeyData;
   }

   public Name { get { return this.monkeyData.Name; } }

   public bool IsYummy(Food food)
   {
      return this.monkeyData.FavoriteFood.Contains(food);
   }

   public MonkeyData GetData()
   {
      // CLONE the DTO here to avoid giving write access to the
      // entity innards without business rule enforcement
      return CloneData(this.monkeyData);
   }

}

Ich habe Dino Espositos neues Buch gefunden Microsoft® .NET: Architekturanwendungen für das Unternehmen ein großartiges Wissensaufwand für diese Art von Fragen und Themen sein.

Die Serviceschicht.

Wenn Sie Ihren Entitäten ein wenig Verhalten hinzufügen möchten, aber Ihre Entitäten nicht ändern können, probieren Sie Erweiterungsmethoden aus. Ich würde dies nur für einfache Szenarien wie in Ihrem Beispiel tun. Alles komplexere oder Koordinaten zwischen mehreren Unternehmen und/oder Diensten, Schichten oder was auch immer in einem Domain -Service sein sollte, wie bereits vorgeschlagen.

Zum Beispiel (aus Ihren Beispielen):

public static class LineItemEntityExtensions
{
  public static decimal CalculateTotal(this LineItemEntity li)
  {
    return li.Quantity * li.Price;
  }
}

public static class OrderEntityExtensions
{
  public static decimal CalculateOrderTotal(this OrderEntity order)
  {
    decimal orderTotal = 0;
    foreach (LineItemEntity li in order.LineItems)
      orderTotal += li.CalculateTotal();
    return orderTotal;
  }
}

public class SomewhereElse
{
  public void DoSomething(OrderEntity order)
  {
    decimal total = order.CalculateOrderTotal();
    ...
  }
}

Wenn es nur sehr wenige dieser Ergänzungen gibt, die Sie möchten, können Sie sie einfach alle in eine "Domainextensions" -Klasse einfügen, aber ich würde ansonsten empfehlen, sie mit regelmäßigem Respekt zu behandeln und alle Erweiterungen eines Unternehmens in einer Klasse in einer eigenen Datei zu behalten .

Zu Ihrer Information: Das einzige Mal, dass ich dies getan habe, war, als ich eine L2S -Lösung hatte und mich nicht mit den Teilern anlegen wollte. Ich hatte auch nicht viele Erweiterungen, weil die Lösung klein war. Ich mag die Idee, mit einer in vollständigen Domain -Dienstenschicht besser zu gehen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top