wake-up-neo.net

Rich vs Anemic Domain Model

Ich entscheide, ob ich ein Rich Domain Model über einem Anemic Domain Model verwenden sollte, und suche nach guten Beispielen für beide.

Ich habe Webanwendungen mit einem anemischen Domänenmodell erstellt, das durch ein Service -> Repository -> Storage-Schichtsystem gesichert wurde, FluentValidation für die BL-Validierung verwendet und alle meine BL in die Service-Schicht gestellt hat.

Ich habe Eric Evans DDD-Buch gelesen, und er (zusammen mit Fowler und anderen) scheint zu glauben, dass Anemic Domain Models ein Anti-Pattern sind.

Ich wollte also wirklich etwas Einblick in dieses Problem bekommen.

Ich bin auch auf der Suche nach guten (grundlegenden) Beispielen für ein Rich Domain Model und die Vorteile gegenüber dem Anemic Domain Model.

69
Sam

Bozhidar Bozhanov scheint im this Blogbeitrag für das anämische Modell zu argumentieren. 

Hier ist die Zusammenfassung, die er präsentiert:

  • domänenobjekte sollten nicht im Frühjahr (IoC) verwaltet werden, sie sollten keine DAOs oder irgendetwas enthalten, das mit der Infrastruktur in Verbindung steht

  • domänenobjekte haben die Domänenobjekte, von denen sie abhängig sind, durch den Ruhezustand (oder den Persistenzmechanismus) festgelegt.

  • domänenobjekte führen die Geschäftslogik aus, da dies die Kernidee von DDD ist. Dies gilt jedoch nicht für Datenbankabfragen oder reine CRUD-Vorgänge für den internen Status des Objekts

  • dTOs werden selten benötigt - in den meisten Fällen handelt es sich bei den Domänenobjekten um die DTOs (was einigen Speichercode erspart).

  • dienste führen CRUD-Vorgänge durch, senden E-Mails, koordinieren die Domänenobjekte, erstellen Berichte basierend auf mehreren Domänenobjekten, führen Abfragen aus usw.

  • die Service-Ebene (Anwendung) ist nicht so dünn, enthält jedoch keine Geschäftsregeln, die den Domänenobjekten eigen sind

  • codegenerierung sollte vermieden werden. Abstraktion, Entwurfsmuster und DI sollten verwendet werden, um die Notwendigkeit der Codegenerierung zu überwinden und letztendlich - um die Duplizierung von Code zu beseitigen.

UPDATE

Ich habe kürzlich this article gelesen, in dem der Autor die Befolgung einer Art hybrider Herangehensweise befürwortet: Domänenobjekte können verschiedene Fragen beantworten, die ausschließlich auf ihrem Status basieren (was bei völlig anämischen Modellen wahrscheinlich in der Service-Schicht erfolgt).

43
geoand

Der Unterschied ist, dass ein anämisches Modell die Logik von den Daten trennt. Die Logik wird oft in Klassen mit den Namen **Service, **Util, **Manager, **Helper usw. platziert. Diese Klassen implementieren die Dateninterpretationslogik und verwenden daher das Datenmodell als Argument. Z.B.

public BigDecimal calculateTotal(Order order){
...
}

während der Rich-Domain-Ansatz dies umkehrt, indem die Dateninterpretationslogik in das Rich-Domain-Modell eingefügt wird. Es fügt also Logik und Daten zusammen und ein reiches Domänenmodell würde folgendermaßen aussehen:

order.getTotal();

Dies hat einen großen Einfluss auf die Objektkonsistenz. Da die Dateninterpretationslogik die Daten umschließt (auf Daten kann nur über Objektmethoden zugegriffen werden), können die Methoden auf Zustandsänderungen anderer Daten reagieren -> Dies nennen wir Verhalten.

In einem anämischen Modell können die Datenmodelle nicht garantieren, dass sie sich in einem legalen Zustand befinden, während sie in einem reichen Domänenmodell dies können. Ein Rich-Domain-Modell wendet OO Prinzipien wie Einkapselung, Ausblenden von Informationen und Zusammenführen von Daten und Logik an. Daher ist ein Anämiemodell ein Antimuster aus einer OO - Perspektive.

Einen tieferen Einblick erhalten Sie in meinem Blog https://www.link-intersystems.com/blog/2011/10/01/anemic-vs-rich-domain-models/

39
René Link

Mein Standpunkt ist folgender:

Anämisches Domänenmodell = Datenbanktabellen, die Objekten zugeordnet sind (nur Feldwerte, kein reales Verhalten)

Rich-Domain-Modell = eine Sammlung von Objekten, die Verhalten offenlegen

Wenn Sie eine einfache CRUD-Anwendung erstellen möchten, reicht möglicherweise ein anämisches Modell mit einem klassischen MVC-Framework aus. Wenn Sie jedoch eine Art Logik implementieren möchten, bedeutet ein anämisches Modell, dass Sie keine objektorientierte Programmierung durchführen.

* Beachten Sie, dass das Objektverhalten nichts mit Persistenz zu tun hat. Eine andere Schicht (Data Mappers, Repositories e.t.c.) ist für das Bestehen von Domänenobjekten verantwortlich.

35
George

Zuerst kopierte ich die Antwort aus diesem Artikel http://msdn.Microsoft.com/de-de/magazine/dn385704.aspx

Abbildung 1 zeigt ein anemisches Domänenmodell, bei dem es sich im Wesentlichen um ein Schema mit Gettern und Setters handelt.

Figure 1 Typical Anemic Domain Model Classes Look Like Database Tables

public class Customer : Person
{
  public Customer()
  {
    Orders = new List<Order>();
  }
  public ICollection<Order> Orders { get; set; }
  public string SalesPersonId { get; set; }
  public ShippingAddress ShippingAddress { get; set; }
}
public abstract class Person
{
  public int Id { get; set; }
  public string Title { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string CompanyName { get; set; }
  public string EmailAddress { get; set; }
  public string Phone { get; set; }
}

In diesem reichhaltigeren Modell werden Eigenschaften, die gelesen und beschrieben werden sollen, nicht einfach aufgedeckt, sondern die öffentliche Oberfläche des Kunden besteht aus expliziten Methoden.

Figure 2 A Customer Type That’s a Rich Domain Model, Not Simply Properties

public class Customer : Contact
{
  public Customer(string firstName, string lastName, string email)
  {
    FullName = new FullName(firstName, lastName);
    EmailAddress = email;
    Status = CustomerStatus.Silver;
  }
  internal Customer()
  {
  }
  public void UseBillingAddressForShippingAddress()
  {
    ShippingAddress = new Address(
      BillingAddress.Street1, BillingAddress.Street2,
      BillingAddress.City, BillingAddress.Region,
      BillingAddress.Country, BillingAddress.PostalCode);
  }
  public void CreateNewShippingAddress(string street1, string street2,
   string city, string region, string country, string postalCode)
  {
    ShippingAddress = new Address(
      street1,street2,
      city,region,
      country,postalCode)
  }
  public void CreateBillingInformation(string street1,string street2,
   string city,string region,string country, string postalCode,
   string creditcardNumber, string bankName)
  {
    BillingAddress = new Address      (street1,street2, city,region,country,postalCode );
    CreditCard = new CustomerCreditCard (bankName, creditcardNumber );
  }
  public void SetCustomerContactDetails
   (string email, string phone, string companyName)
  {
    EmailAddress = email;
    Phone = phone;
    CompanyName = companyName;
  }
  public string SalesPersonId { get; private set; }
  public CustomerStatus Status { get; private set; }
  public Address ShippingAddress { get; private set; }
  public Address BillingAddress { get; private set; }
  public CustomerCreditCard CreditCard { get; private set; }
}
11
Razan Paul

Rich-Domain-Klassen haben den Vorteil, dass Sie ihr Verhalten (Methoden) jedes Mal aufrufen können, wenn Sie den Verweis auf das Objekt in einer beliebigen Ebene haben. Außerdem schreiben Sie meist kleine und verteilte Methoden, die zusammenarbeiten. In anämischen Domänenklassen schreiben Sie in der Regel dicke Verfah- rensmethoden (in der Service-Schicht), die normalerweise vom Anwendungsfall gesteuert werden. Sie sind normalerweise weniger wartungsfähig als Rich-Domain-Klassen.

Ein Beispiel für Domänenklassen mit Verhalten:

class Order {

     String number

     List<OrderItem> items

     ItemList bonus

     Delivery delivery

     void addItem(Item item) { // add bonus if necessary }

     ItemList needToDeliver() { // items + bonus }

     void deliver() {
         delivery = new Delivery()
         delivery.items = needToDeliver()
     }

}

Die Methode needToDeliver() gibt eine Liste der Elemente zurück, die geliefert werden müssen, einschließlich eines Bonus. Sie kann innerhalb der Klasse, von einer anderen verwandten Klasse oder von einer anderen Ebene aufgerufen werden. Wenn Sie zum Beispiel Order zur Ansicht übergeben, können Sie needToDeliver() der ausgewählten Order verwenden, um eine Liste der Elemente anzuzeigen, die vom Benutzer bestätigt werden sollen, bevor er auf die Schaltfläche Speichern klickt, um die Order beizubehalten.

Auf Kommentar antworten

So verwende ich die Domänenklasse vom Controller:

def save = {
   Order order = new Order()
   order.addItem(new Item())
   order.addItem(new Item())
   repository.create(order)
}

Die Erstellung von Order und ihrer LineItem erfolgt in einer Transaktion. Wenn eine der LineItem nicht erstellt werden kann, wird keine Order erstellt.

Ich tendiere dazu, eine Methode zu haben, die eine einzelne Transaktion darstellt, wie:

def deliver = {
   Order order = repository.findOrderByNumber('ORDER-1')
   order.deliver()       
   // save order if necessary
}

Alles in deliver() wird als eine einzige Transaktion ausgeführt. Wenn ich viele unabhängige Methoden in einer einzigen Transaktion ausführen muss, würde ich eine Serviceklasse erstellen.

Um faules Laden zu vermeiden, verwende ich das benannte Entity-Diagramm von JPA 2.1. Zum Beispiel kann ich im Bildschirm Controller für Lieferung eine Methode erstellen, um das Attribut delivery zu laden und bonus zu ignorieren, z. B. repository.findOrderByNumberFetchDelivery(). Im Bonusbildschirm rufe ich eine andere Methode auf, die das Attribut bonus lädt und delivery ignoriert, z. B. repository.findOrderByNumberFetchBonus(). Dies erfordert eine Disziplin, da ich deliver() im Bonusbildschirm immer noch nicht aufrufen kann.

7
jocki

Als ich monolithische Desktop-Apps schrieb, baute ich umfangreiche Domänenmodelle, die ich beim Erstellen von Apps genoss.

Jetzt schreibe ich winzige HTTP-Mikrodienste, es gibt so wenig Code wie möglich, einschließlich anämischer DTOs.

Ich denke, DDD und dieses anämische Argument stammen aus der Zeit der monolithischen Desktop- oder Server-App. Ich erinnere mich an diese Ära und ich stimme zu, dass anämische Modelle seltsam sind. Ich baute eine große monolithische FX-Trading-App und es gab kein Modell, wirklich, es war schrecklich.

Bei Microservices sind die kleinen Dienste mit ihrem reichen Verhalten wohl die zusammensetzbaren Modelle und Aggregate innerhalb einer Domäne. Daher benötigen die Microservice-Implementierungen selbst keine weitere DDD. Die Microservice-Anwendung kann die Domäne sein.

Ein Mikroservice für Bestellungen kann sehr wenige Funktionen haben, ausgedrückt als RESTful-Ressourcen oder über SOAP oder was auch immer. Der Mikroservice-Code für Bestellungen kann extrem einfach sein.

Ein größerer, monolithischer Einzeldienst (Mikro), insbesondere einer, der das Modell im RAM speichert, kann von DDD profitieren.

3
Luke Puplett

Anämische Domänenmodelle sind für ORM und den einfachen Transfer über Netzwerke (das Herzblut aller kommerziellen Anwendungen) wichtig, aber OO ist sehr wichtig für die Verkapselung und Vereinfachung der "Transaktions-/Handhabungskomponenten" Ihres Codes.

Deshalb ist es wichtig, in der Lage zu sein, von einer Welt in die andere umzuwandeln. 

Benennen Sie anemische Modelle wie AnemicUser, UserDAO usw., damit die Entwickler wissen, dass es eine bessere Klasse gibt, die dann verwendet werden kann. Dann haben Sie einen geeigneten Konstruktor für die Klasse ohne Anämien

User(AnemicUser au)

und eine Adaptermethode zum Erstellen der anämischen Klasse für den Transport/die Persistenz

User::ToAnemicUser() 

Bemühen Sie sich, den anämischen Benutzer an keiner Stelle außerhalb des Transports/der Persistenz zu verwenden

0
andrew pate

Hier ist ein Beispiel, das helfen könnte:

Anämie

class Box
{
    public int Height { get; set; }
    public int Width { get; set; }
}

nicht anämisch

class Box
{
    public int Height { get; private set; }
    public int Width { get; private set; }

    public Box(int height, int width)
    {
        if (height <= 0) {
            throw new ArgumentOutOfRangeException(nameof(height));
        }
        if (width <= 0) {
            throw new ArgumentOutOfRangeException(nameof(width));
        }
        Height = height;
        Width = width;
    }

    public int area()
    {
       return Height * Width;
    }
}