Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Tips/Råd
Det här innehållet är ett utdrag från eBook, .NET Microservices Architecture for Containerized .NET Applications, tillgängligt på .NET Docs eller som en kostnadsfri nedladdningsbar PDF som kan läsas offline.
              
               
              
              
            
Komponenter för datapersistence ger åtkomst till data som finns inom gränserna för en mikrotjänst (det vill: en mikrotjänsts databas). De innehåller den faktiska implementeringen av komponenter som lagringsplatser och arbetsenhetsklasser, till exempel anpassade Entity Framework-objekt (EF). DbContext EF DbContext implementerar både Repositorium-mönstret och Arbetsenhet-mönstret.
Lagringsplatsens mönster
Lagringsplatsens mönster är ett Domain-Driven designmönster som är avsett att hålla beständighetsproblem utanför systemets domänmodell. En eller flera persistensabstraktioner – gränssnitt – definieras i domänmodellen, och dessa abstraktioner har implementeringar i form av persistensspecifika adaptrar som definierats någon annanstans i applikationen.
Lagringsplatsimplementeringar är klasser som kapslar in den logik som krävs för att komma åt datakällor. De centraliserar vanliga funktioner för dataåtkomst, vilket ger bättre underhåll och avkoppling av den infrastruktur eller teknik som används för att komma åt databaser från domänmodellen. Om du använder en Object-Relational Mapper (ORM) som Entity Framework förenklas koden som måste implementeras tack vare LINQ och stark skrivning. På så sätt kan du fokusera på logiken för datalagring i stället för det grundläggande arbetet för dataåtkomst.
Repository-mönstret är ett väldokumenterat sätt att arbeta med en datakälla. I boken Patterns of Enterprise Application Architecture beskriver Martin Fowler en lagringsplats på följande sätt:
En lagringsplats utför uppgifter för en mellanhand mellan domänmodelllagren och datamappningen, på ett liknande sätt som en uppsättning domänobjekt i minnet. Klientobjekt skapar deklarativt frågor och skickar dem till lagringsplatserna för att få svar. Konceptuellt kapslar en lagringsplats in en uppsättning objekt som lagras i databasen och åtgärder som kan utföras på dem, vilket ger ett sätt som ligger närmare beständighetsskiktet. Lagringsplatser stöder också syftet med att tydligt och i en riktning separera beroendet mellan arbetsdomänen och dataallokeringen eller mappningen.
Definiera en lagringsplats per aggregering
För varje aggregat eller aggregatroten bör du skapa en klass för lagringsplats. Du kanske kan använda C# Generics för att minska det totala antalet konkreta klasser som du behöver underhålla (vilket visas senare i det här kapitlet). I en mikrotjänst baserad på Domain-Driven designmönster (DDD) bör den enda kanal som du ska använda för att uppdatera databasen vara lagringsplatserna. Det beror på att de har en en-till-en-relation med den aggregerade roten, som styr aggregatets invarianter och transaktionskonsekvens. Det är okej att fråga databasen via andra kanaler (som du kan göra genom att följa en CQRS-metod), eftersom frågor inte ändrar databasens tillstånd. Transaktionsområdet (dvs. uppdateringarna) måste dock alltid styras av lagringsplatserna och de aggregerade rötterna.
En lagringsplats möjliggör populering av data i minnet som hämtas från databasen i form av domänentiteter. När entiteterna finns i minnet kan de ändras och sedan sparas tillbaka till databasen via transaktioner.
Som tidigare nämnts, om du använder arkitekturmönstret CQS/CQRS utförs de första frågorna av sidofrågor från domänmodellen, som utförs av enkla SQL-instruktioner med Dapper. Den här metoden är mycket mer flexibel än lagringsplatser eftersom du kan fråga och ansluta till alla tabeller som du behöver, och dessa frågor begränsas inte av regler från aggregeringarna. Dessa data går till presentationslagret eller klientappen.
Om användaren gör ändringar kommer de data som ska uppdateras från klientappen eller presentationslagret till programskiktet (till exempel en webb-API-tjänst). När du får ett kommando i en kommandohanterare använder du lagringsplatser för att hämta de data som du vill uppdatera från databasen. Du uppdaterar den i minnet med de data som skickas med kommandona och du lägger sedan till eller uppdaterar data (domänentiteter) i databasen via en transaktion.
Det är viktigt att återigen betona att du bara bör definiera en lagringsplats för varje aggregerad rot, enligt bild 7–17. För att uppnå målet med den aggregerade roten för att upprätthålla transaktionell konsekvens mellan alla objekt i aggregerade objekt bör du aldrig skapa en lagringsplats för varje tabell i databasen.
               
              
            
Bild 7-17. Relationen mellan lagringsplatser, aggregeringar och databastabeller
Diagrammet ovan visar relationerna mellan domän- och infrastrukturskikt: Köparens aggregering är beroende av IBuyerRepository och Order Aggregate beror på IOrderRepository-gränssnitten. Dessa gränssnitt implementeras i infrastrukturlagret av motsvarande lagringsplatser som är beroende av UnitOfWork, som också implementeras där, som kommer åt tabellerna på datanivån.
Framtvinga en aggregerad rot per lagringsplats
Det kan vara värdefullt att implementera lagringsplatsens design på ett sådant sätt att den framtvingar regeln att endast aggregerade rötter ska ha lagringsplatser. Du kan skapa en allmän databastyp eller baslagringsplatstyp som begränsar typen av entiteter som den fungerar med för att säkerställa att de har IAggregateRoot markörgränssnittet.
Därför implementerar varje lagringsplatsklass som implementeras på infrastrukturlagret sitt eget kontrakt eller gränssnitt, enligt följande kod:
namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositories
{
    public class OrderRepository : IOrderRepository
    {
      // ...
    }
}
Varje specifikt databasgränssnitt implementerar det generiska IRepository-gränssnittet:
public interface IOrderRepository : IRepository<Order>
{
    Order Add(Order order);
    // ...
}
Ett bättre sätt att få koden att tillämpa konventionen om att varje lagringsplats är relaterad till en enda aggregering är dock att implementera en allmän lagringsplatstyp. På så sätt är det tydligt att du använder en lagringsplats för att rikta in dig på en specifik aggregering. Det kan du enkelt göra genom att implementera ett allmänt IRepository basgränssnitt, som i följande kod:
public interface IRepository<T> where T : IAggregateRoot
{
    //....
}
Mönstret Repository gör det enklare att testa din programlogik
Med Repository-mönstret kan du enkelt testa din applikation med enhetstester. Kom ihåg att enhetstester bara testar din kod, inte infrastruktur, så lagringsplatsens abstraktioner gör det enklare att uppnå det målet.
Som nämnts i ett tidigare avsnitt rekommenderar vi att du definierar och placerar lagringsplatsens gränssnitt i domänmodelllagret så att programskiktet, till exempel din webb-API-mikrotjänst, inte är direkt beroende av infrastrukturskiktet där du har implementerat de faktiska lagringslagerklasserna. Genom att göra detta och använda beroendeinmatning i kontrollanterna för webb-API:et kan du implementera falska lagringsplatser som returnerar falska data i stället för data från databasen. Med den här frikopplade metoden kan du skapa och köra enhetstester som fokuserar logiken i ditt program utan att kräva anslutning till databasen.
Anslutningar till databaser kan misslyckas, och än mer så kan det vara problematiskt att köra hundratals tester mot en databas av två anledningar. För det första kan det ta lång tid på grund av det stora antalet tester. För det andra kan databasposterna ändras och påverka resultatet av dina tester, särskilt om testerna körs parallellt, så att de kanske inte är konsekventa. Enhetstester kan vanligtvis köras parallellt. integreringstester kanske inte stöder parallell körning beroende på deras implementering. Testning mot databasen är inte ett enhetstest utan ett integrationstest. Du bör ha många enhetstester som körs snabbt, men färre integreringstester mot databaserna.
När det gäller separation av problem för enhetstester fungerar din logik på domänentiteter i minnet. Det förutsätter att lagringsplatsklassen har levererat dem. När logiken har ändrat domänentiteterna förutsätter den att lagringsplatsens klass lagrar dem korrekt. Det viktiga här är att skapa enhetstester mot din domänmodell och dess domänlogik. Aggregerade rötter är de viktigaste konsekvensgränserna i DDD.
Lagringsplatserna som implementeras i eShopOnContainers förlitar sig på EF Core DbContext-implementeringen av mönster för lagringsplats och arbetsenhet med hjälp av dess ändringsspårare, så att de inte duplicerar den här funktionen.
Skillnaden mellan mönstret för lagringsplats och det äldre mönstret för dataåtkomstklasser (DAL-klass)
Ett typiskt DAL-objekt utför direkt dataåtkomst och beständighetsåtgärder mot lagring, ofta på samma nivå som en enskild tabell och rad. Enkla CRUD-åtgärder som implementeras med en uppsättning DAL-klasser stöder ofta inte transaktioner (även om detta inte alltid är fallet). De flesta DAL-klassmetoder använder abstraktioner minimalt, vilket resulterar i en nära koppling mellan program- eller BLL-klasser (Business Logic Layer) som anropar DAL-objekten.
När du använder lagringsplatsen kapslas implementeringsinformationen för beständighet in från domänmodellen. Användningen av en abstraktion gör det enkelt att utöka beteendet genom mönster som dekoratörer eller proxyservrar. Till exempel kan övergripande problem som cachelagring, loggning och felhantering tillämpas med hjälp av dessa mönster i stället för hårdkodade i själva dataåtkomstkoden. Det är också enkelt att stödja flera adaptörer för lagringsplats som kan användas i olika miljöer, från lokal utveckling till gemensamma staging-miljöer till produktion.
Implementering av enhet för arbete
En arbetsenhet refererar till en enskild transaktion som omfattar flera infognings-, uppdaterings- eller borttagningsåtgärder. Enkelt uttryckt innebär det att för en specifik användaråtgärd, till exempel en registrering på en webbplats, hanteras alla infognings-, uppdaterings- och borttagningsåtgärder i en enda transaktion. Detta är effektivare än att hantera flera databasåtgärder på ett chattigare sätt.
Dessa flera beständighetshanteringar utförs senare i en enda åtgärd när din kod från applikationsskiktet begär det. Beslutet om att tillämpa minnesinterna ändringar på den faktiska databaslagringen baseras vanligtvis på arbetsenhetsmönstret. I EF implementeras arbetsenhetens mönster av en DbContext och körs när ett anrop görs till SaveChanges.
I många fall kan det här mönstret eller sättet att tillämpa åtgärder mot lagringen öka programmets prestanda och minska risken för inkonsekvenser. Det minskar också transaktionsblockeringen i databastabellerna eftersom alla avsedda åtgärder utförs som en del av en transaktion. Detta är mer effektivt jämfört med att köra många isolerade åtgärder mot databasen. Därför kan den valda ORM:en optimera körningen mot databasen genom att gruppera flera uppdateringsåtgärder inom samma transaktion, i motsats till många små och separata transaktionskörningar.
Arbetsenhetsmönstret kan implementeras med eller utan att använda Depotionsmönstret.
Lagringsplatser bör inte vara obligatoriska
Anpassade lagringsplatser är användbara av de skäl som angavs tidigare, och det är metoden för att beställa mikrotjänster i eShopOnContainers. Det är dock inte ett viktigt mönster att implementera i en DDD-design eller ens i allmän .NET-utveckling.
Jimmy Bogard sa till exempel följande när han gav direkt feedback för den här guiden:
Detta kommer förmodligen att vara min största feedback. Jag gillar verkligen inte förvar, främst för att de döljer de viktiga detaljerna i den underliggande persistensmekanismen. Det är därför jag väljer MediatR för kommandon också. Jag kan använda den fulla kraften i beständighetsskiktet och överföra allt det domänbeteendet till mina aggregater. Jag brukar inte vilja håna mina lagringsplatser - jag behöver fortfarande ha det integrationstestet med den verkliga saken. Att gå CQRS innebar att vi inte riktigt hade något behov av lagringsplatser längre.
Lagringsplatser kan vara användbara, men de är inte viktiga för din DDD-design på det sätt som aggregeringsmönstret och en omfattande domänmodell är. Använd därför Repository-mönstret eller inte, som du tycker passar bäst.
Ytterligare resurser
Databasmönster
- Edward Hieatt och Rob me. Databasmönster. 
 https://martinfowler.com/eaaCatalog/repository.html
- Lagringsplatsens mönster 
 https://free.blessedness.top/previous-versions/msp-n-p/ff649690(v=pandp.10)
- Eric Evans. Domain-Driven Design: Hantera komplexitet i hjärtat av programvaran. (Bok; innehåller en diskussion om lagringsplatsens mönster) 
 https://www.amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/0321125215/
Arbetsenhetsmönster
- Martin Fowler. Arbetsenhetsmönster. 
 https://martinfowler.com/eaaCatalog/unitOfWork.html
- Implementera lagringsplatsen och arbetsenhetens mönster i ett ASP.NET MVC-program 
 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