Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Aanbeveling
Deze inhoud is een fragment uit het eBook, .NET Microservices Architecture for Containerized .NET Applications, beschikbaar op .NET Docs of als een gratis downloadbare PDF die offline kan worden gelezen.
              
              
              
              
            
Onderdelen voor gegevenspersistentie bieden toegang tot de gegevens die worden gehost binnen de grenzen van een microservice (dat wil gezegd, de database van een microservice). Ze bevatten de daadwerkelijke implementatie van onderdelen, zoals opslagplaatsen en Werkklassen, zoals EF-objecten (Custom Entity Framework). DbContext EF DbContext implementeert zowel het Repository- als het Unit of Work-patroon.
Het Repository-patroon
Het patroon Opslagplaats is een Domain-Driven Ontwerppatroon dat is bedoeld om persistentieproblemen buiten het domeinmodel van het systeem te houden. Een of meer persistentieabstracties - interfaces - worden gedefinieerd in het domeinmodel en deze abstracties hebben implementaties in de vorm van persistentiespecifieke adapters die elders in de toepassing zijn gedefinieerd.
Implementaties van opslagplaatsen zijn klassen die de logica inkapselen die nodig zijn voor toegang tot gegevensbronnen. Ze centraliseren de algemene functionaliteit voor gegevenstoegang, wat zorgt voor betere onderhoudbaarheid en het ontkoppelen van de infrastructuur of technologie die wordt gebruikt voor toegang tot databases van het domeinmodel. Als u een Object-Relational-mapper (ORM) zoals Entity Framework gebruikt, wordt dankzij LINQ en sterke typing de te implementeren code vereenvoudigd. Hiermee kunt u zich richten op de logica voor gegevenspersistentie in plaats van op het sanitair voor gegevenstoegang.
Het patroon Opslagplaats is een goed gedocumenteerde manier om met een gegevensbron te werken. In het boek Patterns of Enterprise Application Architecture beschrijft Martin Fowler als volgt een opslagplaats:
Een opslagplaats voert de taken uit van een intermediair tussen de domeinmodellagen en gegevenstoewijzing, die op een vergelijkbare manier fungeren als een set domeinobjecten in het geheugen. Clientobjecten maken declaratief query's en verzenden ze naar de opslagplaatsen voor antwoorden. Conceptueel gezien bevat een opslagplaats een set objecten die zijn opgeslagen in de database en bewerkingen die erop kunnen worden uitgevoerd, op een manier die dichter bij de persistentielaag ligt. Databanken ondersteunen ook het doel van het duidelijk en unidirectioneel scheiden van de afhankelijkheid tussen het werkdomein en de gegevensmapping.
Eén repository per aggregaat definiëren
Voor elk aggregeer of aggregatiewortel moet u één repository-klasse maken. Mogelijk kunt u C# Generics gebruiken om het totale aantal betonklassen te verminderen dat u moet onderhouden (zoals verderop in dit hoofdstuk wordt getoond). In een microservice op basis van Domain-Driven DDD-patronen (Design), moet het enige kanaal dat u moet gebruiken om de database bij te werken, de opslagplaatsen zijn. Dit komt doordat ze een een-op-een-relatie hebben met de aggregaatroot, die de invarianten en transactionele consistentie van het aggregaat beheerst. Het is geen probleem om een query uit te voeren op de database via andere kanalen (zoals u wel kunt doen volgens een CQRS-benadering), omdat query's de status van de database niet wijzigen. Het transactionele gedeelte (dat wil zeggen de updates) moet echter altijd worden beheerd door de repositories en de geaggregeerde hoofdmappen.
In feite kunt u met een opslagplaats gegevens in het geheugen invullen die afkomstig zijn van de database in de vorm van de domeinentiteiten. Zodra de entiteiten zich in het geheugen bevinden, kunnen ze worden gewijzigd en vervolgens via transacties weer in de database worden opgeslagen.
Zoals eerder vermeld, worden de eerste query's uitgevoerd door query's buiten het domeinmodel, uitgevoerd door eenvoudige SQL-instructies met behulp van Dapper als u het architectuurpatroon CQS/CQRS gebruikt. Deze benadering is veel flexibeler dan opslagplaatsen, omdat u tabellen kunt opvragen en samenvoegen die u nodig hebt. Deze query's worden niet beperkt door regels van de aggregaties. Deze gegevens gaan naar de presentatielaag of client-app.
Als de gebruiker wijzigingen aanbrengt, zijn de gegevens die moeten worden bijgewerkt afkomstig van de client-app of presentatielaag naar de toepassingslaag (zoals een web-API-service). Wanneer u een opdracht in een opdrachthandler ontvangt, gebruikt u opslagplaatsen om de gegevens op te halen die u wilt bijwerken uit de database. U werkt deze in het geheugen bij met de gegevens die met de opdrachten zijn doorgegeven en vervolgens voegt u de gegevens (domeinentiteiten) in de database toe of bij via een transactie.
Het is belangrijk om nogmaals te benadrukken dat u slechts één opslagplaats moet definiëren voor elke geaggregeerde hoofdmap, zoals wordt weergegeven in afbeelding 7-17. Als u het doel van de geaggregeerde hoofdmap wilt bereiken om transactionele consistentie tussen alle objecten in de aggregatie te behouden, moet u nooit een opslagplaats maken voor elke tabel in de database.
              
              
            
Afbeelding 7-17. De relatie tussen opslagplaatsen, aggregaties en databasetabellen
In het bovenstaande diagram ziet u de relaties tussen de domein- en infrastructuurlagen: Buyer Aggregate is afhankelijk van de IBuyerRepository en Order Aggregate is afhankelijk van de IOrderRepository-interfaces. Deze interfaces worden in de infrastructuurlaag geïmplementeerd door de bijbehorende repositories die afhangen van UnitOfWork, dat daar ook is geïmplementeerd en toegang heeft tot de tabellen in de gegevenslaag.
Eén geaggregeerde hoofdmap per opslagplaats afdwingen
Het kan waardevol zijn om uw repositorydesign zodanig te implementeren dat de regel wordt afgedwongen dat alleen aggregatiewortels repositories moeten hebben. U kunt een algemeen of basisopslagplaatstype maken waarmee het type entiteiten waarmee deze werkt beperkt om ervoor te zorgen dat ze de IAggregateRoot markeringsinterface hebben.
Elke opslagplaatsklasse die wordt geïmplementeerd op de infrastructuurlaag implementeert dus een eigen contract of interface, zoals wordt weergegeven in de volgende code:
namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories
{
    public class OrderRepository : IOrderRepository
    {
      // ...
    }
}
Elke specifieke opslagplaatsinterface implementeert de algemene IRepository-interface:
public interface IOrderRepository : IRepository<Order>
{
    Order Add(Order order);
    // ...
}
Een betere manier om de code een conventie af te dwingen dat elke opslagplaats is gerelateerd aan één aggregaat, is door een algemeen type opslagplaats te implementeren. Op die manier is het expliciet dat u een repository gebruikt om een specifiek aggregaat te benaderen. Dit kan eenvoudig worden gedaan door een algemene IRepository basisinterface te implementeren, zoals in de volgende code:
public interface IRepository<T> where T : IAggregateRoot
{
    //....
}
Het patroon Opslagplaats maakt het eenvoudiger om uw toepassingslogica te testen
Met het opslagplaatspatroon kunt u uw toepassing eenvoudig testen met eenheidstests. Houd er rekening mee dat eenheidstests alleen uw code testen, niet infrastructuur, dus de abstracties van de opslagplaats maken het gemakkelijker om dat doel te bereiken.
Zoals vermeld in een eerdere sectie, is het raadzaam om de opslagplaatsinterfaces in de domeinmodellaag te definiëren en te plaatsen, zodat de toepassingslaag, zoals uw web-API-microservice, niet rechtstreeks afhankelijk is van de infrastructuurlaag waarin u de werkelijke opslagplaatsklassen hebt geïmplementeerd. Door dit te doen en afhankelijkheidsinjectie te gebruiken in de controllers van uw web-API, kunt u mock-opslagplaatsen implementeren die valse gegevens retourneren in plaats van gegevens uit de database. Met deze losgekoppelde benadering kunt u eenheidstests maken en uitvoeren die de logica van uw toepassing richten zonder dat er verbinding met de database nodig is.
Verbindingen met databases kunnen mislukken en het uitvoeren van honderden tests op een database is om twee redenen slecht. Ten eerste kan het lang duren vanwege het grote aantal tests. Ten tweede kunnen de databaserecords de resultaten van uw tests wijzigen en beïnvloeden, met name als uw tests parallel worden uitgevoerd, zodat ze mogelijk niet consistent zijn. Eenheidstests kunnen doorgaans parallel worden uitgevoerd; integratietests bieden mogelijk geen ondersteuning voor parallelle uitvoering, afhankelijk van hun implementatie. Testen op basis van de database is geen eenheidstest, maar een integratietest. Er moeten veel eenheidstests snel worden uitgevoerd, maar er zijn minder integratietests voor de databases.
Wat betreft de scheiding van verantwoordelijkheden voor unittests, werkt de logica met domeinentiteiten in het geheugen. Hierbij wordt ervan uitgegaan dat de opslagplaatsklasse deze heeft geleverd. Zodra uw logica de domeinentiteiten wijzigt, wordt ervan uitgegaan dat de opslagplaatsklasse ze correct opslaat. Het belangrijkste punt hier is om eenheidstests te maken op basis van uw domeinmodel en de bijbehorende domeinlogica. Aggregatiewortels zijn de belangrijkste consistentiegrenzen in DDD.
De repository-patronen die zijn geïmplementeerd in eShopOnContainers, maken gebruik van de DbContext-implementatie van EF Core van de Repository- en Unit of Work-patronen met de wijzigingentracker, zodat ze deze functionaliteit niet dupliceren.
Het verschil tussen het Repository-patroon en het verouderde gegevenstoegangslaagpatroon (DAL-patroon)
Een typisch DAL-object voert rechtstreeks gegevenstoegangs- en persistentiebewerkingen uit op opslag, vaak op het niveau van één tabel en rij. Eenvoudige CRUD-bewerkingen die worden geïmplementeerd met een set DAL-klassen ondersteunen vaak geen transacties (hoewel dit niet altijd het geval is). De meeste DAL-klassen maken minimaal gebruik van abstracties, wat resulteert in nauwe koppeling tussen toepassings- of BLL-klassen (Business Logic Layer) die de DAL-objecten aanroepen.
Wanneer u opslagplaats gebruikt, worden de implementatiedetails van persistentie ingekapseld van het domeinmodel. Het gebruik van een abstractie biedt het gemak van het uitbreiden van gedrag via patronen zoals Decorators of Proxy's. Zo kunnen kruislingse problemen, zoals caching, logboekregistratie en foutafhandeling, worden toegepast met behulp van deze patronen in plaats van vastgelegd in de code voor gegevenstoegang zelf. Het is ook eenvoudig om meerdere opslagplaatsadapters te ondersteunen die in verschillende omgevingen kunnen worden gebruikt, van lokale ontwikkeling tot gedeelde faseringsomgevingen tot productie.
Eenheid van werk implementeren
Een werkeenheid verwijst naar één transactie waarbij meerdere bewerkingen voor invoegen, bijwerken of verwijderen zijn betrokken. In eenvoudige termen betekent dit dat voor een specifieke gebruikersactie, zoals een registratie op een website, alle invoeg-, update- en verwijderbewerkingen worden verwerkt in één transactie. Dit is efficiënter dan het verwerken van meerdere databasebewerkingen op een chattier manier.
Deze meerdere persistentiebewerkingen worden later in één actie uitgevoerd wanneer uw code van de toepassingslaag deze opdracht geeft. De beslissing over het toepassen van de in-memory wijzigingen op de werkelijke databaseopslag is doorgaans gebaseerd op het Unit of Work-patroon. In EF wordt het Unit of Work-patroon geïmplementeerd door een DbContext en uitgevoerd wanneer een aanroep wordt gedaan naar SaveChanges.
In veel gevallen kan dit patroon of de manier om bewerkingen toe te passen op de opslag de prestaties van de toepassing verbeteren en de kans op inconsistenties verminderen. Het vermindert ook de blokkering van transacties in de databasetabellen, omdat alle beoogde bewerkingen worden doorgevoerd als onderdeel van één transactie. Dit is efficiënter in vergelijking met het uitvoeren van veel geïsoleerde bewerkingen op de database. Daarom kan de geselecteerde ORM de uitvoering optimaliseren op basis van de database door verschillende updateacties binnen dezelfde transactie te groeperen, in tegenstelling tot veel kleine en afzonderlijke transactieuitvoeringen.
Het patroon Unit of Work kan worden geïmplementeerd met of zonder gebruik te maken van het Repository-patroon.
Opslagplaatsen mogen niet verplicht zijn
Aangepaste opslagplaatsen zijn nuttig om de eerder geciteerde redenen en dat is de benadering voor de bestellende microservice in eShopOnContainers. Het is echter geen essentieel patroon om te implementeren in een DDD-ontwerp of zelfs in algemene .NET-ontwikkeling.
Jimmy Bogard, bijvoorbeeld, bij het geven van directe feedback voor deze gids, zei het volgende:
Dit zal waarschijnlijk mijn grootste feedback zijn. Ik ben echt geen fan van opslagplaatsen, vooral omdat ze de belangrijke details van het onderliggende persistentiemechanisme verbergen. Daarom kies ik ook voor MediatR wanneer het gaat om opdrachten. Ik kan de volledige kracht van de persistentielaag gebruiken en al dat domeingedrag naar mijn aggregaatwortels pushen. nl-NL: Ik wil mijn repositories meestal niet mocken – ik moet die integratietest nog steeds uitvoeren met de werkelijke software. Het gaan van CQRS betekende dat we geen opslagplaatsen meer nodig hadden.
Opslagplaatsen kunnen nuttig zijn, maar ze zijn niet essentieel voor uw DDD-ontwerp op de manier waarop het aggregatiespatroon en een uitgebreid domeinmodel zijn. Gebruik daarom het Repository-patroon, of niet, zoals je wilt.
Aanvullende bronnen
Repository-patroon
Edward Hieatt en Rob me. Repository patroon.
https://martinfowler.com/eaaCatalog/repository.htmlRepository-patroon
https://free.blessedness.top/previous-versions/msp-n-p/ff649690(v=pandp.10)Eric Evans. Domain-Driven Ontwerp: Complexiteit aanpakken in het hart van software. (Boek; bevat een bespreking van het Repository-patroon)
https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215/
Werkeenheidpatroon
Martin Fowler. Werkeenheid.
https://martinfowler.com/eaaCatalog/unitOfWork.htmlDe Repository- en Unit of Work-patronen implementeren in een ASP.NET MVC-toepassing
https://free.blessedness.top/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application