Wat is de juiste grootte voor een microservice? U hoort vaak iets van het effect van , "niet te groot en niet te klein" - en hoewel dat zeker klopt, is het in de praktijk niet erg nuttig. Maar als u begint met een zorgvuldig ontworpen domeinmodel, is het veel gemakkelijker om te redenleren over microservices.
In dit artikel wordt een droneleveringsservice als voorbeeld gebruikt. Meer informatie over het scenario en de bijbehorende referentie-implementatie vindt u hier.
Van domeinmodel naar microservices
In het vorige artikel hebben we een set begrensde contexten gedefinieerd voor een Drone Delivery-toepassing. Vervolgens hebben we een van deze afhankelijke contexten, de context Verzending gebonden, nader bekeken en een set entiteiten, aggregaties en domeinservices voor die gebonden context geïdentificeerd.
U bent nu klaar om van het domeinmodel naar het toepassingsontwerp te gaan. Hier volgt een aanpak voor het afleiden van microservices van het domeinmodel.
Begin met een contextgrens. Het is gewoonlijk het beste als de functionaliteit van een microservice binnen één contextgrens blijft. Per definitie geeft een contextgrens de grens van een bepaald domeinmodel aan. Als u ontdekt dat in een microservice verschillende domeinmodellen zijn samengevoegd, is dat een teken dat u misschien terug moet gaan om uw domeinanalyse te verfijnen.
Bekijk vervolgens de aggregaties in uw domeinmodel. Aggregaties zijn vaak goede kandidaten voor microservices. Een goed ontworpen aggregatie vertoont veel kenmerken van een goed ontworpen microservice, zoals:
- Een aggregatie is eerder afgeleid van zakelijke vereisten dan van technische beslommeringen zoals gegevenstoegang of berichtgeving.
- Een aggregatie moet een grote functionele cohesie hebben.
- Een aggregatie is een persistentiegrens.
- Aggregaties moeten losjes worden gekoppeld.
Domeinservices zijn ook goede kandidaten voor microservices. Domeinservices zijn bewerkingen zonder status tussen meerdere aggregaties. Een typisch voorbeeld is een werkstroom waarbij meerdere microservices betrokken zijn. Een voorbeeld hiervan zullen we tegenkomen in de Drone Delivery-toepassing.
Denk ten slotte na over niet-functionele vereisten. Kijk naar factoren zoals de teamgrootte, gegevenstypen, technologieën, schaalbaarheid, beschikbaarheidsvereisten en beveiligingsvereisten. Op basis van deze factoren besluit u misschien een microservice verder op te splitsen in twee of meer kleinere services, of om daarentegen verschillende microservices samen te voegen.
Nadat u de microservices in uw toepassing hebt geïdentificeerd, valideert u uw ontwerp aan de volgende criteria:
- Elke service heeft één verantwoordelijkheid.
- Er zijn geen chatgesprekken tussen services. Als het splitsen van functionaliteit in twee services ertoe leidt dat ze te veel worden gebruikt, kan het een symptoom zijn dat deze functies deel uitmaken van dezelfde service.
- Elke service is klein genoeg om te worden gebouwd door een klein team dat zelfstandig werkt.
- Er zijn geen afhankelijkheden waarvoor twee of meer services in de vergrendelingsstap moeten worden geïmplementeerd. Het moet altijd mogelijk zijn om een service te implementeren zonder andere services opnieuw te implementeren.
- Services zijn niet nauw gekoppeld en kunnen onafhankelijk van elkaar worden ontwikkeld.
- Uw servicegrenzen veroorzaken geen problemen met gegevensconsistentie of integriteit. Soms is het belangrijk om gegevensconsistentie te behouden door functionaliteit in één microservice te plaatsen. Dat gezegd hebbende, moet u overwegen of u echt sterke consistentie nodig hebt. Er zijn strategieën voor het aanpakken van uiteindelijke consistentie in een gedistribueerd systeem, en de voordelen van het opsplitsen van services wegen vaak zwaarder dan de uitdagingen van het beheren van uiteindelijke consistentie.
Het is vooral belangrijk om pragmatisch te zijn en voor ogen te houden dat Domain-Driven Design een iteratief proces is. Als u twijfelt kunt u het beste beginnen met grotere microservices. Het splitsen van een microservice in twee kleinere services is eenvoudiger dan functies te herstructureren in meerdere bestaande microservices.
Voorbeeld: Microservices definiëren voor de Drone Delivery-toepassing
Zoals u zich herinnert, heeft het ontwikkelteam de vier aggregaties geïdentificeerd: Levering, Pakket, Drone en Account, en twee domeinservices, Scheduler en Supervisor.
Levering en Pakket zijn voor de hand liggende kandidaten voor microservices. De Scheduler en Supervisor coördineren de activiteiten die door andere microservices worden uitgevoerd, zodat het zinvol is om deze domeinservices als microservices te implementeren.
Drone en Account zijn interessant omdat ze deel uitmaken van andere gebonden contexten. Een optie is dat de Scheduler de contexten drone- en account gebonden rechtstreeks aanroept. Een andere optie is het maken van drone- en accountmicroservices binnen de context Verzendings gebonden. Deze microservices bemiddelen tussen de begrensde contexten door API's of gegevensschema's beschikbaar te maken die meer geschikt zijn voor de verzendcontext.
De details van de contexten Drone en Account gebonden vallen buiten het bereik van deze richtlijnen. Daarom hebben we hiervoor gesimuleerde services gemaakt in onze referentie-implementatie. Maar hier zijn enkele factoren om in deze situatie rekening mee te houden:
Wat is de netwerkoverhead van het rechtstreeks aanroepen in de andere gebonden context?
Is het gegevensschema voor de andere afhankelijke context geschikt voor deze context of is het beter om een schema te hebben dat is afgestemd op deze gebonden context?
Is de andere gebonden context een verouderd systeem? Als dat het zo is, kunt u een service maken die fungeert als een anti-corruptielaag om te zetten tussen het verouderde systeem en de moderne toepassing.
Wat is de teamstructuur? Is het gemakkelijk om te communiceren met het team dat verantwoordelijk is voor de andere gebonden context? Zo niet, dan kan het maken van een service die tussen de twee contexten bemiddelt helpen om de kosten van communicatie tussen teams te beperken.
Tot nu toe hebben we geen niet-functionele vereisten in overweging genomen. Na te denken over de doorvoervereisten van de toepassing, heeft het ontwikkelteam besloten om een afzonderlijke microservice voor opname te maken die verantwoordelijk is voor het opnemen van clientaanvragen. Deze microservice implementeert load leveling door binnenkomende aanvragen in een buffer te plaatsen voor verwerking. De Scheduler leest de aanvragen uit de buffer en voert de werkstroom uit.
Niet-functionele vereisten leidden ertoe dat het team één extra service heeft gemaakt. Alle services tot nu toe hebben betrekking op het proces van het plannen en leveren van pakketten in realtime. Maar het systeem moet ook de geschiedenis van elke levering opslaan in langetermijnopslag voor gegevensanalyse. Het team heeft overwogen dit de verantwoordelijkheid van de Delivery-service te maken. De vereisten voor gegevensopslag zijn echter heel anders voor historische analyses dan voor in-flight-bewerkingen (zie Gegevensoverwegingen). Daarom heeft het team besloten om een afzonderlijke service voor de leveringsgeschiedenis te maken, die luistert naar DeliveryTracking-gebeurtenissen van de Delivery-service en de gebeurtenissen naar langetermijnopslag schrijft.
In het volgende diagram ziet u het ontwerp op dit punt:
Een Visio-bestand van deze architectuur downloaden.
Volgende stappen
Op dit punt moet u een duidelijk inzicht hebben in het doel en de functionaliteit van elke microservice in uw ontwerp. U kunt nu het systeem ontwerpen.