Entwerfen der Persistenzebene der InfrastrukturDesign the infrastructure persistence layer

Die Komponenten der Datenpersistenz bieten Zugriff auf die Daten, die innerhalb der Grenzen eines Microservices gehostet werden (d. h. in der Datenbank eines Microservices).Data persistence components provide access to the data hosted within the boundaries of a microservice (that is, a microservice's database). Sie enthalten die tatsächliche Implementierung von Komponenten wie Repositorys und Arbeitseinheits-Klassen wie benutzerdefinierte Entity Framework-DbContext-Objekte.They contain the actual implementation of components such as repositories and Unit of Work classes, like custom Entity Framework (EF) DbContext objects. EF-DbContext implementiert sowohl das Repository- als auch das Arbeitseinheitsmuster.EF DbContext implements both, the Repository and the Unit of Work patterns.

Das RepositorymusterThe Repository pattern

Repositorys sind Klassen oder Komponenten, welche die für den Zugriff auf Datenquellen erforderliche Logik einschließen.Repositories are classes or components that encapsulate the logic required to access data sources. Sie zentralisieren allgemeine Funktionen für den Datenzugriff und bieten dabei eine bessere Verwaltbarkeit und Entkopplung der Infrastruktur oder Technologie, die für den Zugriff auf Datenbanken über die Domänenmodellebene verwendet wird.They centralize common data access functionality, providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer. Wenn Sie einen ORM (objektrelationaler Mapper) wie Entity Framework verwenden, wird der zu implementierende Code dank LINQ und starker Typisierung vereinfacht.If you use an Object-Relational Mapper (ORM) like Entity Framework, the code that must be implemented is simplified, thanks to LINQ and strong typing. Dadurch können Sie sich auf die Logik der Datenpersistenz konzentrieren und müssen sich nicht mit der Grundstruktur des Datenzugriffs befassen.This lets you focus on the data persistence logic rather than on data access plumbing.

Das Repositorymuster stellt eine Methode mit umfassender Dokumentation für die Arbeit mit einer Datenquelle dar.The Repository pattern is a well-documented way of working with a data source. Im Buch Patterns of Enterprise Application Architecture (Muster der Architektur von Unternehmensanwendungen) beschreibt Martin Fowler ein Repository wie folgt:In the book Patterns of Enterprise Application Architecture, Martin Fowler describes a repository as follows:

Ein Repository führt die Tasks eines Zwischenspeichers zwischen den Domänenmodellebenen und der Datenzuordnung aus und agiert dabei ähnlich wie eine Reihe von Domänenobjekten im Arbeitsspeicher.A repository performs the tasks of an intermediary between the domain model layers and data mapping, acting in a similar way to a set of domain objects in memory. Clientobjekte erstellen deklarativ Abfragen und senden diese an die Repositorys, um Antworten darauf zu bekommen.Client objects declaratively build queries and send them to the repositories for answers. Im Prinzip schließt ein Repository eine Reihe von Objekten, die in der Datenbank gespeichert sind, sowie Vorgänge ein, die darin ausgeführt werden können, und bietet damit eine Möglichkeit, die näher an der Persistenzebene liegt.Conceptually, a repository encapsulates a set of objects stored in the database and operations that can be performed on them, providing a way that is closer to the persistence layer. Zudem unterstützen Repositorys den Zweck der eindeutigen und unidirektionalen Trennung der Abhängigkeit zwischen der Arbeitsdomäne und der Datenzuordnung.Repositories, also, support the purpose of separating, clearly and in one direction, the dependency between the work domain and the data allocation or mapping.

Definieren eines Repositorys pro AggregatDefine one repository per aggregate

Sie sollten für jedes Aggregat bzw. für jeden Aggregatstamm eine Repositoryklasse erstellen.For each aggregate or aggregate root, you should create one repository class. In einem auf domänengesteuerten Entwurfsmustern (DDD) basierenden Microservice sollten Sie für das Update der Datenbank als einzigen Kanal die Repositorys verwenden.In a microservice based on Domain-Driven Design (DDD) patterns, the only channel you should use to update the database should be the repositories. Der Grund dafür ist, dass sie in einer 1:1-Beziehung zum Aggregatstamm stehen, wodurch die Invarianten und die Transaktionskonsistenz des Aggregats gesteuert werden.This is because they have a one-to-one relationship with the aggregate root, which controls the aggregate's invariants and transactional consistency. Die Datenbank kann auch über andere Kanäle abgefragt werden (nach einem CQRS-Ansatz), da sich der Status der Datenbank bei Abfragen nicht ändert.It's okay to query the database through other channels (as you can do following a CQRS approach), because queries don't change the state of the database. Der Transaktionsbereich (d.h. die Updates) muss jedoch immer von den Repositorys und den Aggregatstämmen gesteuert werden.However, the transactional area (that is, the updates) must always be controlled by the repositories and the aggregate roots.

Im Wesentlichen können Sie in einem Repository den Arbeitsspeicher mit Daten auffüllen, die aus der Datenbank in Form von Domänenentitäten stammen.Basically, a repository allows you to populate data in memory that comes from the database in the form of the domain entities. Sobald sich die Entitäten im Arbeitsspeicher befinden, können sie geändert und über Transaktionen wieder in der Datenbank gespeichert werden.Once the entities are in memory, they can be changed and then persisted back to the database through transactions.

Wie bereits erwähnt wurde, werden bei Verwendung des CQS/CQRS-Architekturmusters die ersten Abfragen mit Abfragen aus dem Domänenmodell über einfache SQL-Anweisungen mit Dapper ausgeführt.As noted earlier, if you're using the CQS/CQRS architectural pattern, the initial queries are performed by side queries out of the domain model, performed by simple SQL statements using Dapper. Dieser Ansatz ist flexibler als Repositorys, da Sie alle erforderlichen Tabelle abfragen und verknüpfen können und diese Abfragen nicht durch Regeln aus den Aggregaten eingeschränkt werden.This approach is much more flexible than repositories because you can query and join any tables you need, and these queries aren't restricted by rules from the aggregates. Diese Daten sind dann auf der Darstellungsschicht oder in der Client-App enthalten.That data goes to the presentation layer or client app.

Wenn der Benutzer Änderungen vornimmt, werden die zu aktualisierenden Daten von der Client-App oder der Darstellungsschicht auf die Anwendungsschicht (z.B. ein Web-API-Dienst) verschoben.If the user makes changes, the data to be updated comes from the client app or presentation layer to the application layer (such as a Web API service). Wenn Sie in einem Befehlshandler einen Befehl empfangen, rufen Sie die Daten, die Sie aus der Datenbank aktualisieren möchten, über Repositorys ab.When you receive a command in a command handler, you use repositories to get the data you want to update from the database. Sie aktualisieren die Daten im Arbeitsspeicher mit den Daten, die mit den Befehlen übergeben wurden, und können die Daten (Domänenentitäten) anschließend in der Datenbank über eine Transaktion hinzufügen oder aktualisieren.You update it in memory with the data passed with the commands, and you then add or update the data (domain entities) in the database through a transaction.

Beachten Sie, dass Sie pro Aggregatstamm nur ein Repository definieren dürfen (s. Abbildung 7-17).It's important to emphasize again that you should only define one repository for each aggregate root, as shown in Figure 7-17. Damit das Ziel des Aggregatstamms erreicht wird und die Transaktionskonsistenz zwischen allen Objekten im Aggregat erhalten bleibt, sollten Sie niemals für jede Tabelle in der Datenbank ein Repository erstellen.To achieve the goal of the aggregate root to maintain transactional consistency between all the objects within the aggregate, you should never create a repository for each table in the database.

Diagramm, das Beziehungen zwischen der Domäne und anderer Infrastruktur zeigt.

Abbildung 7-17.Figure 7-17. Die Beziehung zwischen Repositorys, Aggregaten und DatenbanktabellenThe relationship between repositories, aggregates, and database tables

Das obige Diagramm zeigt die Beziehungen zwischen Domänen- und Infrastrukturebenen: Das Buyer-Aggregat hängt vom Aggregat IBuyerRepository und das Order-Aggregat von den IOrderRepository-Schnittstellen ab. Diese Schnittstellen werden in der Infrastrukturebene über die entsprechenden Repositorys implementiert, die von UnitOfWork abhängen. UnitOfWork ist ebenfalls dort implementiert und greift auf die Tabellen in der Datenschicht zu.The above diagram shows the relationships between Domain and Infrastructure layers: Buyer Aggregate depends on the IBuyerRepository and Order Aggregate depends on the IOrderRepository interfaces, these interfaces are implemented in the Infrastructure layer by the corresponding repositories that depend on UnitOfWork, also implemented there, that accesses the tables in the Data tier.

Erzwingen eines Aggregatstamms pro RepositoryEnforce one aggregate root per repository

Es kann sinnvoll sein, Ihren Repositoryentwurf so zu implementieren, dass die Regel erzwungen wird, nach der nur Aggregatstämme über Repositorys verfügen sollten.It can be valuable to implement your repository design in such a way that it enforces the rule that only aggregate roots should have repositories. Sie können einen generischen oder grundlegenden Repositorytyp erstellen, der den Entitätstyp einschränkt, mit dem er arbeitet, um sicherzustellen, dass die Entitäten die IAggregateRoot-Markerschnittstelle aufweisen.You can create a generic or base repository type that constrains the type of entities it works with to ensure they have the IAggregateRoot marker interface.

Daher implementiert jede auf der Infrastrukturebene implementierte Repositoryklasse wie im folgenden Code dargestellt ihren eigenen Vertrag bzw. ihre eigene Schnittstelle:Thus, each repository class implemented at the infrastructure layer implements its own contract or interface, as shown in the following code:

namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories
{
    public class OrderRepository : IOrderRepository
    {
      // ...
    }
}

Jede spezifische Repositoryschnittstelle implementiert die generische IRepository-Schnittstelle:Each specific repository interface implements the generic IRepository interface:

public interface IOrderRepository : IRepository<Order>
{
    Order Add(Order order);
    // ...
}

Allerdings besteht im Implementieren eines generischen Repositorytyps eine bessere Möglichkeit, damit der Code die Konvention erzwingt, dass jedes Repository mit einem einzelnen Aggregat verknüpft ist.However, a better way to have the code enforce the convention that each repository is related to a single aggregate is to implement a generic repository type. Auf diese Weise ist explizit festgelegt, dass Sie ein Repository für ein bestimmtes Aggregat verwenden.That way, it's explicit that you're using a repository to target a specific aggregate. Dies kann problemlos wie im folgenden Code dargestellt durch Implementierung einer generischen IRepository-Basisschnittstelle geschehen:That can be easily done by implementing a generic IRepository base interface, as in the following code:

public interface IRepository<T> where T : IAggregateRoot
{
    //....
}

Das Repositorymuster erleichtert das Testen Ihrer AnwendungslogikThe Repository pattern makes it easier to test your application logic

Das Repositorymuster bietet die Möglichkeit, Ihre Anwendung ohne großen Aufwand mithilfe von Komponententests zu testen.The Repository pattern allows you to easily test your application with unit tests. Beachten Sie, dass bei Komponententests nur Ihr Code getestet wird, nicht die Infrastruktur, damit dieses Ziel durch die Repositoryabstraktionen leichter erreicht werden kann.Remember that unit tests only test your code, not infrastructure, so the repository abstractions make it easier to achieve that goal.

Wie in einem Abschnitt weiter oben erwähnt wurde, wird empfohlen, die Repositoryschnittstellen auf der Domänenmodellebene zu definieren und anzuordnen, damit die Anwendungsschicht, z.B. Ihr Web-API-Microservice, nicht direkt von der Infrastrukturebene abhängt, auf der Sie die tatsächlichen Repositoryklassen implementiert haben.As noted in an earlier section, it's recommended that you define and place the repository interfaces in the domain model layer so the application layer, such as your Web API microservice, doesn't depend directly on the infrastructure layer where you've implemented the actual repository classes. Auf diese Weise und durch Verwendung der Abhängigkeitsinjektion in den Controllern Ihrer Web-API können Sie Pseudorepositorys implementieren, die anstelle von Daten aus der Datenbank falsche Daten zurückgeben.By doing this and using Dependency Injection in the controllers of your Web API, you can implement mock repositories that return fake data instead of data from the database. Mit diesem entkoppelten Ansatz können Sie Komponententests erstellen und ausführen, die die Logik Ihrer Anwendung fokussieren, ohne dass eine Verbindung zur Datenbank erforderlich ist.This decoupled approach allows you to create and run unit tests that focus the logic of your application without requiring connectivity to the database.

Verbindungen zu Datenbanken können fehlschlagen, und aus zwei Gründen ist von der Durchführung von Hunderten von Tests in einer Datenbank abzuraten.Connections to databases can fail and, more importantly, running hundreds of tests against a database is bad for two reasons. Zunächst einmal kann dies aufgrund der zahlreichen Tests viel Zeit in Anspruch nehmen.First, it can take a long time because of the large number of tests. Zudem könnten sich die Datensätze in der Datenbank ändern. Dies könnte Auswirkungen auf die Ergebnisse Ihrer Tests haben, sodass diese möglicherweise nicht konsistent sind.Second, the database records might change and impact the results of your tests, so that they might not be consistent. Das Testen der Datenbank ist kein Komponententest, sondern ein Integrationstest.Testing against the database isn't a unit test but an integration test. Für die Datenbanken sollten viele schnelle Komponententests, aber wenige Integrationstest durchgeführt werden.You should have many unit tests running fast, but fewer integration tests against the databases.

Im Hinblick auf die Abgrenzung von Problemen bei Komponententests verwaltet Ihre Logik Domänenentitäten im Arbeitsspeicher.In terms of separation of concerns for unit tests, your logic operates on domain entities in memory. Es wird davon ausgegangen, dass die Repositoryklasse diese Entitäten übermittelt hat.It assumes the repository class has delivered those. Sobald Ihre Logik die Domänenentitäten ändert, wird davon ausgegangen, dass diese ordnungsgemäß in der Repositoryklasse gespeichert werden.Once your logic modifies the domain entities, it assumes the repository class will store them correctly. Wichtig dabei ist, dass Komponententests für Ihr Domänenmodell und die zugehörige Domänenlogik erstellt werden.The important point here is to create unit tests against your domain model and its domain logic. Aggregatstämme stellen die hauptsächlichen Konsistenzgrenzen in DDD dar.Aggregate roots are the main consistency boundaries in DDD.

Die in eShopOnContainers implementierten Repositorys basieren auf der EF Core-DbContext-Implementierung von Repository- und Arbeitseinheitsmustern unter Verwendung der Änderungsnachverfolgung, sodass diese Funktionalität nicht dupliziert wird.The repositories implemented in eShopOnContainers rely on EF Core's DbContext implementation of the Repository and Unit of Work patterns using its change tracker, so they don't duplicate this functionality.

Der Unterschied zwischen dem Repositorymuster und dem älteren Muster der Datenzugriffsklasse (DAL-Klasse)The difference between the Repository pattern and the legacy Data Access class (DAL class) pattern

Ein Datenzugriffsobjekt führt direkt Datenzugriffs- und Persistenzvorgänge für den Speicher aus.A data access object directly performs data access and persistence operations against storage. Ein Repository markiert die Daten mit den Vorgängen, die Sie im Arbeitsspeicher eines Arbeitseinheitsobjekts (wie in EF beim Verwenden der DbContext-Klasse) durchführen möchten. Diese Updates werden jedoch nicht sofort für die Datenbank ausgeführt.A repository marks the data with the operations you want to perform in the memory of a unit of work object (as in EF when using the DbContext class), but these updates aren't performed immediately to the database.

Eine Arbeitseinheit wird als einzelne Transaktion bezeichnet, die mehrere INSERT-, UPDATE- oder DELETE-Vorgänge umfasst.A unit of work is referred to as a single transaction that involves multiple insert, update, or delete operations. Einfach ausgedrückt bedeutet das, dass alle Einfüge-, Update- und Löschvorgänge für eine bestimmte Benutzeraktion, z.B. die Registrierung auf einer Website, in einer einzelnen Transaktion verarbeitet werden.In simple terms, it means that for a specific user action, such as a registration on a website, all the insert, update, and delete operations are handled in a single transaction. Dies ist effizienter als mehrere Datenbanktransaktionen auf umständlichere Weise zu verarbeiten.This is more efficient than handling multiple database transactions in a chattier way.

Diese Persistenzvorgänge werden zu einem späteren Zeitpunkt in einer einzelnen Aktion durchgeführt, wenn Ihr Code aus der Anwendungsebene dies anordnet.These multiple persistence operations are performed later in a single action when your code from the application layer commands it. Die Entscheidung, ob die im Speicher vorgenommenen Änderungen am tatsächlichen Datenbankspeicher angewendet werden sollen, basiert in der Regel auf dem Arbeitseinheitsmuster.The decision about applying the in-memory changes to the actual database storage is typically based on the Unit of Work pattern. In EF wird das Arbeitseinheitsmuster als DbContext implementiert.In EF, the Unit of Work pattern is implemented as the DbContext.

In vielen Fällen kann dieses Muster bzw. diese Methode zum Anwenden von Vorgängen im Speicher die Anwendungsleistung erhöhen und die Gefahr von Inkonsistenzen verringern.In many cases, this pattern or way of applying operations against the storage can increase application performance and reduce the possibility of inconsistencies. Zudem können Transaktionsblockierungen in den Datenbanktabellen verringert werden, da alle vorgesehenen Vorgänge im Rahmen einer Transaktion zugesichert werden.It also reduces transaction blocking in the database tables, because all the intended operations are committed as part of one transaction. Im Vergleich zur Ausführung vieler isolierter Vorgänge in der Datenbank ist dies der effizientere Weg.This is more efficient in comparison to executing many isolated operations against the database. Daher kann die ausgewählte ORM die Ausführung für die Datenbank optimieren, indem statt der Ausführung vieler kleiner und separater Transaktionen mehrere Updateaktionen innerhalb derselben Transaktion gruppiert werden.Therefore, the selected ORM can optimize the execution against the database by grouping several update actions within the same transaction, as opposed to many small and separate transaction executions.

Repositorys sollten nicht obligatorisch sein.Repositories shouldn't be mandatory

Benutzerdefinierte Repositorys sind aus den oben genannten Gründen hilfreich, und dies ist der Ansatz für den Microservice „Ordering“ (Bestellung) in eShopOnContainers.Custom repositories are useful for the reasons cited earlier, and that is the approach for the ordering microservice in eShopOnContainers. Es ist jedoch kein essentielles Muster, das in einem domänengesteuerten Entwurf oder in der allgemeinen .NET-Entwicklung implementiert werden muss.However, it isn't an essential pattern to implement in a DDD design or even in general .NET development.

Jimmy Bogard hat beispielsweise Folgendes geäußert, als er direktes Feedback zu diesem Leitfaden gegeben hat:For instance, Jimmy Bogard, when providing direct feedback for this guide, said the following:

Dies wird wahrscheinlich mein wichtigstes Feedback.This'll probably be my biggest feedback. Ich bin wirklich kein Fan von Repositorys. Das liegt hauptsächlich daran, dass sie die wichtigen Details des zugrunde liegenden Persistenzmechanismus verstecken.I'm really not a fan of repositories, mainly because they hide the important details of the underlying persistence mechanism. Deswegen vertraue ich auch bei Befehlen auf MediatR.It's why I go for MediatR for commands, too. Ich kann die volle Leistung der Persistenzebene nutzen und das gesamte Domänenverhalten per Push in meine Aggregatstämme übertragen.I can use the full power of the persistence layer, and push all that domain behavior into my aggregate roots. Normalerweise möchte ich keine Repositorys simulieren. Ich muss diese Integrationstests weiterhin am Original durchführen können.I don't usually want to mock my repositories – I still need to have that integration test with the real thing. Durch die Einführung von CQRS sind Repositorys nicht mehr zwingend erforderlich.Going CQRS meant that we didn't really have a need for repositories any more.

Repositorys können nützlich sein, sind jedoch nicht so wichtig für Ihren domänengesteuerten Entwurf wie Aggregatsmuster und umfassende Domänenmodelle.Repositories might be useful, but they are not critical for your DDD design, in the way that the Aggregate pattern and rich domain model are. Daher können Sie selbst entscheiden, ob Sie das Repositorymuster verwenden möchten oder nicht.Therefore, use the Repository pattern or not, as you see fit. Dennoch verwenden Sie das Repositorymuster immer, wenn Sie EF Core verwenden, obwohl das Repository in diesem Fall den gesamten Microservice oder die Kontextgrenze abdeckt.Anyway, you'll be using the repository pattern whenever you use EF Core although, in this case, the repository covers the whole microservice or bounded context.

Zusätzliche RessourcenAdditional resources

RepositorymusterRepository pattern

ArbeitseinheitsmusterUnit of Work pattern