Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Tipp
Dieser Inhalt ist ein Auszug aus dem eBook .NET Microservices Architecture for Containerized .NET Applications, verfügbar auf .NET Docs oder als kostenlose herunterladbare PDF, die offline gelesen werden kann.
Im vorherigen Abschnitt wurden die grundlegenden Entwurfsprinzipien und -muster für das Entwerfen eines Domänenmodells erläutert. Jetzt ist es an der Zeit, mögliche Möglichkeiten zur Implementierung des Domänenmodells mithilfe von .NET (nur C#-Code) und EF Core zu erkunden. Ihr Domänenmodell besteht einfach aus Ihrem Code. Es wird nur die EF Core-Modellanforderungen haben, aber keine echten Abhängigkeiten von EF. Sie sollten keine harten Abhängigkeiten oder Verweise auf EF Core oder andere ORM in Ihrem Domänenmodell haben.
Domänenmodellstruktur in einer benutzerdefinierten .NET-Standardbibliothek
Die Ordnerorganisation, die für die Referenzanwendung "eShopOnContainers" verwendet wird, veranschaulicht das DDD-Modell für die Anwendung. Möglicherweise stellen Sie fest, dass eine andere Ordnerorganisation die für Ihre Anwendung getroffenen Entwurfsentscheidungen deutlicher kommuniziert. Wie Sie in Abbildung 7-10 sehen können, gibt es im Bestelldomänenmodell zwei Aggregate, das Bestellaggregat und das Käuferaggregat. Jedes Aggregat ist eine Gruppe von Domänenentitäten und Wertobjekten, obwohl Sie auch ein Aggregat aus einer einzelnen Domänenentität (dem Aggregatstamm oder der Stammentität) haben könnten.
Die Solution Explorer-Ansicht für das Projekt "Ordering.Domain", zeigt den Ordner "AggregatesModel", der die Ordner "BuyerAggregate" und "OrderAggregate" enthält, die jeweils ihre Entitätsklassen, Wertobjektdateien und so weiter enthalten.
Abbildung 7-10. Domänenmodellstruktur für den Bestell-Microservice in eShopOnContainers
Darüber hinaus umfasst die Ebene des Domänenmodells die Schnittstellen der Repositorys, die als Infrastrukturanforderungen für Ihr Domänenmodell gelten. Mit anderen Worten: Diese Schnittstellen geben an, welche Repositorys und welche Methoden die Infrastrukturebene implementieren muss. Es ist wichtig, dass die Implementierung der Repositorys außerhalb der Domänenmodellschicht in der Infrastrukturschicht-Bibliothek platziert wird, sodass die Domänenmodellebene nicht durch APIs oder Klassen von Infrastrukturtechnologien wie dem Entity Framework "verunreinigt" wird.
Sie können auch einen SeedWork-Ordner sehen, der benutzerdefinierte Basisklassen enthält, die Sie als Basis für Ihre Domänenentitäten und Wertobjekte verwenden können, sodass Sie keinen redundanten Code in der Objektklasse jeder Domäne haben.
Strukturaggregate in einer benutzerdefinierten .NET Standardbibliothek
Ein Aggregat bezieht sich auf einen Cluster von Domänenobjekten, die gruppiert sind, um die Transaktionskonsistenz abzugleichen. Diese Objekte können Instanzen von Entitäten sein (eine davon ist der Aggregatstamm oder die Stammentität) zusätzlich zu anderen Wertobjekten.
Transaktionskonsistenz bedeutet, dass ein Aggregat am Ende einer Geschäftsaktion konsistent und aktuell ist. Beispielsweise wird das Bestellaggregat aus dem eShopOnContainers-Bestell-Microservice-Domänenmodell zusammengesetzt, wie in Abbildung 7-11 dargestellt.
Eine detaillierte Ansicht des Ordners OrderAggregate: Address.cs ist ein Wertobjekt, IOrderRepository ist eine Repositoryschnittstelle, Order.cs ist ein Aggregatstamm, OrderItem.cs ist eine untergeordnete Entität und OrderStatus.cs ist eine Enumerationsklasse.
Abbildung 7-11. Das Bestellaggregat in der Visual Studio-Lösung
Wenn Sie eine der Dateien in einem Aggregatordner öffnen, können Sie sehen, wie sie als benutzerdefinierte Basisklasse oder Schnittstelle wie Entitäts- oder Wertobjekt gekennzeichnet ist, wie im SeedWork-Ordner implementiert.
Implementieren von Domänenentitäten als POCO-Klassen
Sie implementieren ein Domänenmodell in .NET, indem Sie POCO-Klassen erstellen, die Ihre Domänenentitäten implementieren. Im folgenden Beispiel wird die Order-Klasse als Entität und auch als Aggregatstamm definiert. Da die Order-Klasse von der Entity-Basisklasse abgeleitet ist, kann sie gemeinsamen Code für Entitäten wiederverwenden. Denken Sie daran, dass diese Basisklassen und Schnittstellen im Domänenmodellprojekt von Ihnen definiert werden, daher handelt es sich um Ihren Code, nicht um Infrastrukturcode aus einem ORM wie EF.
// COMPATIBLE WITH ENTITY FRAMEWORK CORE 5.0
// Entity is a custom base class with the ID
public class Order : Entity, IAggregateRoot
{
private DateTime _orderDate;
public Address Address { get; private set; }
private int? _buyerId;
public OrderStatus OrderStatus { get; private set; }
private int _orderStatusId;
private string _description;
private int? _paymentMethodId;
private readonly List<OrderItem> _orderItems;
public IReadOnlyCollection<OrderItem> OrderItems => _orderItems;
public Order(string userId, Address address, int cardTypeId, string cardNumber, string cardSecurityNumber,
string cardHolderName, DateTime cardExpiration, int? buyerId = null, int? paymentMethodId = null)
{
_orderItems = new List<OrderItem>();
_buyerId = buyerId;
_paymentMethodId = paymentMethodId;
_orderStatusId = OrderStatus.Submitted.Id;
_orderDate = DateTime.UtcNow;
Address = address;
// ...Additional code ...
}
public void AddOrderItem(int productId, string productName,
decimal unitPrice, decimal discount,
string pictureUrl, int units = 1)
{
//...
// Domain rules/logic for adding the OrderItem to the order
// ...
var orderItem = new OrderItem(productId, productName, unitPrice, discount, pictureUrl, units);
_orderItems.Add(orderItem);
}
// ...
// Additional methods with domain rules/logic related to the Order aggregate
// ...
}
Es ist wichtig zu beachten, dass dies eine Domänenentität ist, die als POCO-Klasse implementiert ist. Es hat keine direkte Abhängigkeit von Entity Framework Core oder einem anderen Infrastrukturframework. Diese Implementierung ist wie in DDD, nur C#-Code, der ein Domänenmodell implementiert.
Darüber hinaus ist die Klasse mit einer Schnittstelle namens "IAggregateRoot" versehen. Diese Schnittstelle ist eine leere Schnittstelle, die manchmal als Markerschnittstelle bezeichnet wird, die nur verwendet wird, um anzugeben, dass diese Entitätsklasse auch ein Aggregatstamm ist.
Eine Markierungsschnittstelle wird manchmal als Antimuster betrachtet; Es ist jedoch auch eine saubere Möglichkeit, eine Klasse zu markieren, insbesondere, wenn sich diese Schnittstelle entwickeln kann. Ein Attribut könnte die andere Wahl für die Markierung sein, aber es ist schneller, die Basisklasse (Entity) neben der IAggregate-Schnittstelle anzuzeigen, anstatt einen Aggregate-Attributmarker über der Klasse zu platzieren. Es geht in jedem Fall um Vorlieben.
Ein Aggregatstamm bedeutet, dass der Großteil des Codes im Zusammenhang mit Konsistenz und Geschäftsregeln der Entitäten des Aggregats als Methoden in der Order-Aggregatstammklasse implementiert werden soll (z. B. AddOrderItem beim Hinzufügen eines OrderItem-Objekts zum Aggregat). Sie sollten OrderItems-Objekte weder unabhängig noch direkt erstellen oder aktualisieren; Die AggregateRoot-Klasse muss die Kontrolle und Konsistenz eines Aktualisierungsvorgangs mit seinen untergeordneten Entitäten behalten.
Daten in Domänenentitäten kapseln
Ein häufiges Problem in Entitätsmodellen besteht darin, dass sie Sammlungsnavigationseigenschaften als öffentlich zugängliche Listentypen verfügbar machen. Dadurch kann jedes Mitglied des Entwicklerteams die Inhalte dieser Auflistungstypen ändern. Dabei können möglicherweise wichtige Geschäftsregeln umgangen werden, die im Zusammenhang mit der Auflistung stehen, wodurch das Objekt im Status „ungültig“ hinterlassen wird. Die Lösung hierfür besteht darin, schreibgeschützten Zugriff auf verwandte Sammlungen bereitzustellen und explizit Methoden zur Verfügung zu stellen, die definieren, wie Clients diese bearbeiten können.
Beachten Sie im vorherigen Code, dass viele Attribute schreibgeschützt oder privat sind und nur von den Klassenmethoden aktualisiert werden können, sodass jedes Update geschäftsdomäneninvarianten und Logik berücksichtigt, die in den Klassenmethoden angegeben sind.
Die folgenden DDD-Muster sollten z. B. nicht von einer Befehlshandlermethode oder Anwendungsschichtklasse ausgeführt werden (tatsächlich sollte dies unmöglich sein):
// WRONG ACCORDING TO DDD PATTERNS – CODE AT THE APPLICATION LAYER OR
// COMMAND HANDLERS
// Code in command handler methods or Web API controllers
//... (WRONG) Some code with business logic out of the domain classes ...
OrderItem myNewOrderItem = new OrderItem(orderId, productId, productName,
pictureUrl, unitPrice, discount, units);
//... (WRONG) Accessing the OrderItems collection directly from the application layer // or command handlers
myOrder.OrderItems.Add(myNewOrderItem);
//...
In diesem Fall ist die Add-Methode rein ein Vorgang zum Hinzufügen von Daten mit direktem Zugriff auf die OrderItems-Auflistung. Aus diesem Grund wird ein Großteil der Domänenlogik, Regeln oder Validierungen, der im Zusammenhang mit diesem Vorgang mit den untergeordneten Entitäten steht, auf die Anwendungsebene verteilt (Befehlshandler und Web-API-Controller).
Wenn Sie den Aggregatstamm umgehen, kann der Aggregatstamm seine Invarianten, seine Gültigkeit oder seine Konsistenz nicht garantieren. So wird Ihr Code mit der Zeit sehr unübersichtlich, oder es entsteht Transaktionsskriptcode.
Wenn Entitäten über öffentliche Setter in einer Entitätseigenschaft verfügen, steht dies im Widerspruch zu den Mustern des domänengesteuerten Designs. Änderungen in einer Entität sollten durch explizite Methoden mit klarer und einheitlicher Sprache über die Änderungen, die sie in der Entität ausführen, gesteuert werden.
Außerdem soll es sich bei Auflistungen innerhalb der Entität (wie die der Bestellelemente) um schreibgeschützte Eigenschaften handeln (also um die nachfolgend erläuterte AsReadOnly-Methode). Sie sollten die Entität nur innerhalb der Methoden der Aggregatstammklasse oder der Methoden der untergeordneten Klasse aktualisieren können.
Wie Sie im Aggregatstamm „Order“ sehen können, sollten alle Setter den Status „privat“ aufweisen oder zumindest extern schreibgeschützt sein, sodass jeder Vorgang für die Daten der Entität oder der untergeordneten Entitäten über Methoden in der Entitätsklasse ausgeführt werden muss. Dadurch wird Konsistenz auf kontrollierte und objektorientierte Weise beibehalten, anstatt Transaktionsskriptcode zu implementieren.
Der folgende Codeausschnitt zeigt die richtige Methode zum Codieren der Aufgabe zum Hinzufügen eines OrderItem-Objekts zum Order-Aggregat.
// RIGHT ACCORDING TO DDD--CODE AT THE APPLICATION LAYER OR COMMAND HANDLERS
// The code in command handlers or WebAPI controllers, related only to application stuff
// There is NO code here related to OrderItem object's business logic
myOrder.AddOrderItem(productId, productName, pictureUrl, unitPrice, discount, units);
// The code related to OrderItem params validations or domain rules should
// be WITHIN the AddOrderItem method.
//...
In diesem Ausschnitt wird ein Großteil der Validierungen oder Logik, die im Zusammenhang mit dem OrderItem-Objekt stehen, vom Aggregatstamm „Order“ über die Methode „AddOrderItem“ gesteuert. Dies gilt insbesondere für Validierungen und Logik, die im Zusammenhang mit anderen Elementen im Aggregat stehen. Beispielsweise erhalten Sie möglicherweise denselben Produktartikel als Ergebnis mehrerer Aufrufe von AddOrderItem. In dieser Methode können Sie die Produktelemente untersuchen und dieselben Produktelemente in ein einzelnes OrderItem-Objekt mit mehreren Einheiten konsolidieren. Wenn es unterschiedliche Rabattbeträge gibt, aber die Produkt-ID identisch ist, würden Sie wahrscheinlich den höheren Rabatt anwenden. Dieses Prinzip gilt für jede andere Domänenlogik für das OrderItem-Objekt.
Darüber hinaus wird der neue OrderItem(params)-Vorgang auch von der AddOrderItem-Methode aus dem Order-Aggregatstamm gesteuert und ausgeführt. Daher befinden sich die meisten Logik oder Überprüfungen im Zusammenhang mit diesem Vorgang (insbesondere alle Elemente, die sich auf die Konsistenz zwischen anderen untergeordneten Entitäten auswirken) an einer zentralen Stelle innerhalb des Aggregatstamms. Das ist der ultimative Zweck des Aggregatstammmusters.
Wenn Sie Entity Framework Core 1.1 oder höher verwenden, kann eine DDD-Entität besser ausgedrückt werden, da sie das Zuordnen von Feldern zusätzlich zu Eigenschaften ermöglicht. Dies ist nützlich, wenn Auflistungen von untergeordneten Entitäten oder Wertobjekten geschützt werden sollen. Mit dieser Erweiterung können Sie einfache private Felder anstelle von Eigenschaften verwenden und jede Aktualisierung der Feldsammlung in öffentlichen Methoden implementieren und schreibgeschützten Zugriff über die AsReadOnly-Methode bereitstellen.
In DDD möchten Sie die Entität nur über Methoden in der Entität (oder den Konstruktor) aktualisieren, um alle invarianten Elemente und die Konsistenz der Daten zu steuern, sodass Eigenschaften nur mit einem Get-Accessor definiert werden. Die Eigenschaften werden durch private Datenfelder gesichert. Auf private Members kann nur innerhalb einer Klasse zugegriffen werden. Es gibt jedoch eine Ausnahme: EF Core muss diese Felder ebenfalls festlegen (sodass es das Objekt mit den richtigen Werten zurückgeben kann).
Zuordnen von Eigenschaften zu Feldern in der Datenbanktabelle mit get-Accessors
Das Zuordnen von Eigenschaften zu Datenbanktabellenspalten ist keine Domänenverantwortung, sondern Teil der Infrastruktur- und Persistenzschicht. Wir erwähnen dies hier nur, damit Sie die neuen Funktionen in EF Core 1.1 oder höher im Zusammenhang mit der Modellierung von Entitäten kennen. Weitere Details zu diesem Thema finden Sie im Abschnitt "Infrastruktur und Persistenz".
Wenn Sie EF Core 1.0 oder höher verwenden, müssen Sie in dbContext die Eigenschaften zuordnen, die nur mit Getters zu den tatsächlichen Feldern in der Datenbanktabelle definiert sind. Dies geschieht mit der HasField-Methode der PropertyBuilder-Klasse.
Zuordnen von Feldern ohne Eigenschaften
Wenn das Feature in EF Core 1.1 oder höher Spalten zu Feldern zuordnen soll, ist es auch möglich, keine Eigenschaften zu verwenden. Stattdessen können Sie Spalten aus einer Tabelle zu Feldern zuordnen. Ein gängiger Anwendungsfall hierfür sind private Felder für einen internen Zustand, auf den nicht von außerhalb der Entität zugegriffen werden muss.
Im vorherigen OrderAggregate-Codebeispiel gibt es mehrere private Felder. Ein Beispiel ist das Feld _paymentMethodId
, für das keine zugehörige Eigenschaft für einen Setter oder Getter existiert. Dieses Feld kann auch innerhalb der Geschäftslogik der Bestellung berechnet und aus den Methoden der Bestellung verwendet werden, muss aber auch in der Datenbank beibehalten werden. Daher gibt es in EF Core (seit v1.1) eine Möglichkeit, ein Feld ohne zugehörige Eigenschaft einer Spalte in der Datenbank zuzuordnen. Dies wird auch im Abschnitt "Infrastrukturebene " dieses Handbuchs erläutert.
Weitere Ressourcen
Vaughn Vernon. Modellieren von Aggregaten mit DDD und Entity Framework. Beachten Sie, dass dies kein Entity Framework Core ist.
https://kalele.io/blog-posts/modeling-aggregates-with-ddd-and-entity-framework/Julie Lerman. Datenpunkte – Codieren für Domain-Driven Design: Tipps für Data-Focused Devs
https://learn.microsoft.com/archive/msdn-magazine/2013/august/data-points-coding-for-domain-driven-design-tips-for-data-focused-devsUdi Dahan. So erstellen Sie vollständig gekapselte Domänenmodelle
https://udidahan.com/2008/02/29/how-to-create-fully-encapsulated-domain-models/Steve Smith. Was ist der Unterschied zwischen einem DTO und einem POCO? \ https://ardalis.com/dto-or-poco/