Delen via


Een DDD-georiënteerde microservice ontwerpen

Tip

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.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

Domeingestuurd ontwerp (DDD) pleit voor modellering op basis van de realiteit van het bedrijf als relevant voor uw gebruiksscenario's. In de context van het bouwen van toepassingen praat DDD over problemen als domeinen. Het beschrijft onafhankelijke probleemgebieden als gebonden contexten (elke gebonden context correleert met een microservice) en benadrukt een algemene taal om over deze problemen te praten. Er worden ook veel technische concepten en patronen voorgesteld, zoals domeinentiteiten met uitgebreide modellen (geen anemic-domeinmodel), waardeobjecten, aggregaties en statistische basisregels (of hoofdentiteiten) ter ondersteuning van de interne implementatie. In deze sectie wordt het ontwerp en de implementatie van deze interne patronen geïntroduceerd.

Soms worden deze DDD-technische regels en -patronen gezien als obstakels die een steile leercurve hebben voor het implementeren van DDD-benaderingen. Maar het belangrijke onderdeel is niet de patronen zelf, maar het ordenen van de code, zodat deze is afgestemd op de zakelijke problemen en dezelfde zakelijke termen gebruikt (alomtegenwoordige taal). Daarnaast moeten DDD-benaderingen alleen worden toegepast als u complexe microservices met aanzienlijke bedrijfsregels implementeert. Eenvoudigere verantwoordelijkheden, zoals een CRUD-service, kunnen worden beheerd met eenvoudigere benaderingen.

Waar de grenzen moeten worden getekend, is de belangrijkste taak bij het ontwerpen en definiëren van een microservice. DDD-patronen helpen u inzicht te hebben in de complexiteit van het domein. Voor het domeinmodel voor elke gebonden context identificeert en definieert u de entiteiten, waardeobjecten en aggregaties die uw domein modelleren. U bouwt en verfijnt een domeinmodel dat zich bevindt binnen een grens die uw context definieert. En dat is expliciet in de vorm van een microservice. De onderdelen binnen deze grenzen zijn uw microservices, hoewel in sommige gevallen een BC- of zakelijke microservices uit verschillende fysieke services kunnen bestaan. DDD gaat over grenzen en dus microservices.

De grenzen van de microservicecontext relatief klein houden

Bepalen waar grenzen tussen gebonden contexten moeten worden geplaatst, worden twee concurrerende doelen in balans gebracht. Eerst wilt u in eerste instantie de kleinste microservices maken, hoewel dat niet het belangrijkste stuurprogramma moet zijn; U moet een grens creëren rond zaken die samenhang nodig hebben. Ten tweede wilt u chatty communicatie tussen microservices voorkomen. Deze doelen kunnen elkaar tegenspreken. U moet deze in balans brengen door het systeem op te heffen in zo veel kleine microservices als u kunt totdat u ziet dat de communicatiegrenzen snel groeien met elke extra poging om een nieuwe gebonden context te scheiden. Cohesie is essentieel binnen één context.

Het is vergelijkbaar met de ongepaste vertrouwelijkheid code geur bij het implementeren van klassen. Als twee microservices veel met elkaar moeten samenwerken, moeten ze waarschijnlijk dezelfde microservice zijn.

Een andere manier om naar dit aspect te kijken is autonomie. Als een microservice moet vertrouwen op een andere service om een aanvraag rechtstreeks te kunnen verwerken, is deze niet echt autonoom.

Lagen in DDD-microservices

De meeste bedrijfstoepassingen met een aanzienlijke zakelijke en technische complexiteit worden gedefinieerd door meerdere lagen. De lagen zijn een logisch artefact en zijn niet gerelateerd aan de implementatie van de service. Ze bestaan om ontwikkelaars te helpen de complexiteit in de code te beheren. Verschillende lagen (zoals de domeinmodellaag versus de presentatielaag, enzovoort) kunnen verschillende typen hebben, waardoor vertalingen tussen deze typen worden verplicht.

Een entiteit kan bijvoorbeeld worden geladen vanuit de database. Vervolgens kan een deel van die informatie, of een aggregatie van informatie, inclusief aanvullende gegevens van andere entiteiten, worden verzonden naar de gebruikersinterface van de client via een REST Web API. Het punt hier is dat de domeinentiteit zich in de domeinmodellaag bevindt en niet mag worden doorgegeven aan andere gebieden waartoe de entiteit niet behoort, zoals de presentatielaag.

Daarnaast moet u altijd geldige entiteiten hebben (zie de sectie Validaties ontwerpen in de sectie Domeinmodellaag ) die worden beheerd door aggregaties (hoofdentiteiten). Daarom mogen entiteiten niet worden gebonden aan clientweergaven, omdat sommige gegevens mogelijk nog steeds niet worden gevalideerd op gebruikersinterfaceniveau. Dit is de reden waarom het ViewModel bedoeld is. ViewModel is een gegevensmodel dat uitsluitend geschikt is voor de behoeften van de presentatielaag. De domeinentiteiten behoren niet rechtstreeks tot het ViewModel. In plaats daarvan moet u vertalen tussen ViewModels en domeinentiteiten en vice versa.

Bij het aanpakken van complexiteit is het belangrijk dat een domeinmodel wordt beheerd door geaggregeerde hoofdmappen, zodat alle invarianten en regels met betrekking tot die groep entiteiten (aggregaties) worden uitgevoerd via één ingangspunt of -poort, de hoofdmap van de aggregaties.

Afbeelding 7-5 laat zien hoe een gelaagd ontwerp wordt geïmplementeerd in de toepassing eShopOnContainers.

Diagram showing the layers in a domain-driven design microservice.

Afbeelding 7-5. DDD-lagen in de bestellende microservice in eShopOnContainers

De drie lagen in een DDD-microservice zoals Bestellen. Elke laag is een VS-project: toepassingslaag is Ordering.API, domeinlaag is Ordering.Domain en de infrastructuurlaag is Ordering.Infrastructure. U wilt het systeem zo ontwerpen dat elke laag alleen communiceert met bepaalde andere lagen. Deze aanpak kan eenvoudiger worden afgedwongen als lagen worden geïmplementeerd als verschillende klassebibliotheken, omdat u duidelijk kunt bepalen welke afhankelijkheden tussen bibliotheken zijn ingesteld. De domeinmodellaag mag bijvoorbeeld geen afhankelijkheid hebben van een andere laag (de domeinmodelklassen moeten gewone oude klasseobjecten of POCO-klassen zijn). Zoals wordt weergegeven in afbeelding 7-6, heeft de layer-bibliotheek Ordering.Domain alleen afhankelijkheden van de .NET-bibliotheken of NuGet-pakketten, maar niet op een andere aangepaste bibliotheek, zoals gegevensbibliotheek of persistentiebibliotheek.

Screenshot of Ordering.Domain dependencies.

Afbeelding 7-6. Lagen die zijn geïmplementeerd als bibliotheken, bieden betere controle over afhankelijkheden tussen lagen

De domeinmodellaag

Eric Evans's uitstekende boek Domain Driven Design zegt het volgende over de domeinmodellaag en de toepassingslaag.

Domeinmodellaag: verantwoordelijk voor het vertegenwoordigen van concepten van het bedrijf, informatie over de bedrijfssituatie en bedrijfsregels. Staat die de bedrijfssituatie weerspiegelt, wordt hier beheerd en gebruikt, ook al worden de technische details van het opslaan ervan gedelegeerd aan de infrastructuur. Deze laag is het hart van bedrijfssoftware.

De domeinmodellaag is de plek waar het bedrijf wordt uitgedrukt. Wanneer u een microservicedomeinmodellaag in .NET implementeert, wordt die laag gecodeerd als een klassebibliotheek met de domeinentiteiten die gegevens plus gedrag vastleggen (methoden met logica).

Na de principes van persistentie-genegeerdheid en infrastructuur-negeren, moet deze laag gegevenspersistentiedetails volledig negeren. Deze persistentietaken moeten worden uitgevoerd door de infrastructuurlaag. Daarom mag deze laag geen directe afhankelijkheden van de infrastructuur aannemen, wat betekent dat een belangrijke regel is dat de entiteitsklassen van uw domeinmodel POCO's moeten zijn.

Domeinentiteiten mogen geen directe afhankelijkheid hebben (zoals afgeleid van een basisklasse) voor een infrastructuurframework voor gegevenstoegang, zoals Entity Framework of NHibernate. In het ideale geval mogen uw domeinentiteiten niet worden afgeleid van of het implementeren van een type dat is gedefinieerd in een infrastructuurframework.

De meeste moderne ORM-frameworks, zoals Entity Framework Core, bieden deze benadering, zodat uw domeinmodelklassen niet aan de infrastructuur zijn gekoppeld. Het gebruik van POCO-entiteiten is echter niet altijd mogelijk bij het gebruik van bepaalde NoSQL-databases en -frameworks, zoals Actors en Reliable Collections in Azure Service Fabric.

Zelfs als het belangrijk is om het principe persistentie negeren voor uw domeinmodel te volgen, moet u persistentieproblemen niet negeren. Het is nog steeds belangrijk om inzicht te hebben in het fysieke gegevensmodel en hoe het wordt toegewezen aan uw entiteitsobjectmodel. Anders kunt u onmogelijke ontwerpen maken.

Dit aspect betekent ook niet dat u een model kunt gebruiken dat is ontworpen voor een relationele database en deze rechtstreeks naar een NoSQL- of documentgeoriënteerde database verplaatst. In sommige entiteitsmodellen kan het model passen, maar meestal niet. Er zijn nog steeds beperkingen waaraan uw entiteitsmodel moet voldoen, op basis van de opslagtechnologie en ORM-technologie.

De toepassingslaag

Verdergaand naar de toepassingslaag, kunnen we het boek Domain Driven Design van Eric Evans opnieuw citeren:

Toepassingslaag: Definieert de taken die de software moet uitvoeren en stuurt de expressieve domeinobjecten om problemen op te lossen. De taken waarvoor deze laag verantwoordelijk is, zijn zinvol voor het bedrijf of nodig voor interactie met de toepassingslagen van andere systemen. Deze laag wordt dun gehouden. Het bevat geen bedrijfsregels of kennis, maar coördineert alleen taken en gedelegeerden werken aan samenwerkingen van domeinobjecten in de volgende laag omlaag. De status heeft geen weergave van de bedrijfssituatie, maar kan een status hebben die de voortgang van een taak voor de gebruiker of het programma weerspiegelt.

De toepassingslaag van een microservice in .NET wordt doorgaans gecodeerd als een ASP.NET Core Web API-project. Het project implementeert de interactie van de microservice, externe netwerktoegang en de externe web-API's die worden gebruikt vanuit de gebruikersinterface of client-apps. Het omvat query's als u een CQRS-benadering gebruikt, opdrachten die worden geaccepteerd door de microservice en zelfs de gebeurtenisgestuurde communicatie tussen microservices (integratiegebeurtenissen). De ASP.NET Core Web-API die de toepassingslaag vertegenwoordigt, mag geen bedrijfsregels of domeinkennis bevatten (met name domeinregels voor transacties of updates); deze moeten eigendom zijn van de klassebibliotheek van het domeinmodel. De toepassingslaag mag alleen taken coördineren en mag geen domeinstatus (domeinmodel) bevatten of definiëren. Het delegeert de uitvoering van bedrijfsregels naar de domeinmodelklassen zelf (geaggregeerde roots en domeinentiteiten), die uiteindelijk de gegevens binnen die domeinentiteiten bijwerken.

In feite is de toepassingslogica de plek waar u alle use cases implementeert die afhankelijk zijn van een bepaalde front-end. Bijvoorbeeld de implementatie met betrekking tot een web-API-service.

Het doel is dat de domeinlogica in de domeinmodellaag, de invarianten, het gegevensmodel en gerelateerde bedrijfsregels volledig onafhankelijk moeten zijn van de presentatie- en toepassingslagen. De domeinmodellaag mag vooral niet rechtstreeks afhankelijk zijn van een infrastructuurframework.

De infrastructuurlaag

De infrastructuurlaag is hoe de gegevens die in eerste instantie worden bewaard in domeinentiteiten (in het geheugen) worden bewaard in databases of een ander permanent archief. Een voorbeeld is het gebruik van Entity Framework Core-code voor het implementeren van de opslagplaatspatroonklassen die een DBContext gebruiken om gegevens in een relationele database te behouden.

In overeenstemming met de eerder genoemde principes van persistentie- en infrastructuur-negeren mag de infrastructuurlaag de domeinmodellaag niet "verontreinigen". U moet de entiteitsklassen van het domeinmodel agnostisch houden van de infrastructuur die u gebruikt om gegevens (EF of een ander framework) vast te houden door geen harde afhankelijkheden op frameworks te nemen. De klassebibliotheek van uw domeinmodellaag mag alleen uw domeincode bevatten, alleen POCO-entiteitsklassen die het hart van uw software implementeren en volledig losgekoppeld zijn van infrastructuurtechnologieën.

Uw lagen of klassebibliotheken en -projecten moeten dus uiteindelijk afhankelijk zijn van uw domeinmodellaag (bibliotheek), niet omgekeerd, zoals wordt weergegeven in afbeelding 7-7.

Diagram showing dependencies that exist between DDD service layers.

Afbeelding 7-7. Afhankelijkheden tussen lagen in DDD

Afhankelijkheden in een DDD-service, de toepassingslaag is afhankelijk van domein en infrastructuur en infrastructuur is afhankelijk van domein, maar domein is niet afhankelijk van een laag. Dit laagontwerp moet onafhankelijk zijn voor elke microservice. Zoals eerder vermeld, kunt u de meest complexe microservices implementeren volgens DDD-patronen, terwijl u eenvoudigere gegevensgestuurde microservices (eenvoudige CRUD in één laag) op een eenvoudigere manier implementeert.

Aanvullende bronnen