Patroon

Ervoor zorgen dat elk onderdeel van het systeem deelneemt aan het besluitvormingsproces over de werkstroom van een bedrijfstransactie, in plaats van te vertrouwen op een centraal beheerpunt.

Context en probleem

In microservicesarchitectuur is het vaak het geval dat een cloudtoepassing is onderverdeeld in verschillende kleine services die samenwerken om een zakelijke transactie end-to-end te verwerken. Om de koppeling tussen services te verlagen, is elke service verantwoordelijk voor één bedrijfsbewerking. Enkele voordelen zijn snellere ontwikkeling, kleinere codebasis en schaalbaarheid. Het ontwerpen van een efficiënte en schaalbare werkstroom is echter een uitdaging en vereist vaak complexe communicatie tussen de service.

De services communiceren met elkaar met behulp van goed gedefinieerde API's. Zelfs één bedrijfsbewerking kan leiden tot meerdere point-to-point-aanroepen tussen alle services. Een veelvoorkomende communicatiepatroon is het gebruik van een gecentraliseerde service die als de orchestrator fungeert. Het bevestigt alle inkomende aanvragen en delegeert bewerkingen aan de respectieve services. Hierdoor wordt ook de werkstroom van de hele zakelijke transactie beheert. Elke service voltooit alleen een bewerking en is niet op de hoogte van de algehele werkstroom.

Het orchestratorpatroon vermindert punt-naar-puntcommunicatie tussen services, maar heeft enkele nadelen vanwege de nauwe koppeling tussen de orchestrator en andere services die deel nemen aan de verwerking van de zakelijke transactie. Als u taken in een reeks wilt uitvoeren, moet de orchestrator enige domeinkennis hebben over de verantwoordelijkheden van deze services. Als u services wilt toevoegen of verwijderen, wordt de bestaande logica verstoord en moet u delen van het communicatiepad opnieuw bekabelen. Hoewel u de werkstroom eenvoudig kunt configureren en services eenvoudig kunt toevoegen of verwijderen met een goed ontworpen orchestrator, is een dergelijke implementatie complex en moeilijk te onderhouden.

Een aanvraag verwerken met behulp van een centrale orchestrator

Oplossing

Laat elke service bepalen wanneer en hoe een bedrijfsbewerking wordt verwerkt, in plaats van afhankelijk te zijn van een centrale orchestrator.

Een manier om een bedrijf te implementeren, is door het asynchrone berichtenpatroon te gebruiken om de bedrijfsactiviteiten te coördineren.

Een aanvraag verwerken met behulp van een

Een clientaanvraag publiceert berichten naar een berichtenwachtrij. Wanneer berichten binnenkomen, worden ze naar abonnees of services gestuurd die geïnteresseerd zijn in dat bericht. Elke geabonneerde service doet zijn bewerking zoals aangegeven door het bericht en reageert op de berichtenwachtrij met succes of mislukt van de bewerking. In geval van succes kan de service een bericht terug pushen naar dezelfde wachtrij of een andere berichtenwachtrij, zodat een andere service indien nodig de werkstroom kan voortzetten. Als een bewerking mislukt, kan de berichtenbus die bewerking opnieuw uitvoeren.

Op deze manier wordt de werkstroom door de services onderling verdeeld zonder dat er een orchestrator of directe communicatie tussen de services nodig is.

Omdat er geen punt-naar-puntcommunicatie is, helpt dit patroon de koppeling tussen services te verminderen. Het kan ook het prestatieknelpunt verwijderen dat wordt veroorzaakt door de orchestrator wanneer deze alle transacties moet verwerken.

Wanneer dit patroon gebruiken

Gebruik het niet-dynamische patroon als u verwacht regelmatig nieuwe services bij te werken, te verwijderen of toe te voegen. De hele app kan met minder inspanning en minimale onderbreking van bestaande services worden gewijzigd.

Houd rekening met dit patroon als u prestatieknelpunten in de centrale orchestrator ervaart.

Dit patroon is een natuurlijk model voor de serverloze architectuur waarbij alle services van korte duur of gebeurtenisgestuurd kunnen zijn. Services kunnen worden gemaakt vanwege een gebeurtenis, hun taak uitvoeren en worden verwijderd wanneer de taak is voltooid.

Problemen en overwegingen

Het gedecentraliseerd maken van de orchestrator kan problemen veroorzaken tijdens het beheren van de werkstroom.

Als een service een bedrijfsbewerking niet kan voltooien, kan het lastig zijn om van die fout te herstellen. Eén manier is om de service een fout te laten aangeven door een gebeurtenis te laten afvuren. Een andere service die zich op deze mislukte gebeurtenissen abonneert, neemt noodzakelijke acties, zoals het toepassen van compenserende transacties om geslaagde bewerkingen in een aanvraag ongedaan te maken. De mislukte service kan ook geen gebeurtenis voor de fout veroorzaken. In dat geval kunt u overwegen een mechanisme voor opnieuw proberen en of een time-out te gebruiken om die bewerking als een fout te herkennen. Zie de sectie Voorbeeld voor een voorbeeld.

Het is eenvoudig om een werkstroom te implementeren wanneer u onafhankelijke bedrijfsactiviteiten parallel wilt verwerken. U kunt één berichtenbus gebruiken. De werkstroom kan echter ingewikkeld worden wanneer er sprake moet zijn van een opeenvolgend proces. Service C kan bijvoorbeeld pas worden uitgevoerd nadat service A en service B hun bewerkingen met succes hebben voltooid. Een aanpak is om meerdere berichtenbusjes te hebben die berichten in de vereiste volgorde ontvangen. Zie de sectie Voorbeeld voor meer informatie.

Het volgroeide patroon wordt een uitdaging als het aantal services snel toeneemt. Gezien het grote aantal onafhankelijke bewegende onderdelen, wordt de werkstroom tussen services meestal complex. Gedistribueerde tracering wordt ook moeilijk.

De orchestrator beheert de tolerantie van de werkstroom centraal en kan een single point of failure worden. Aan de andere kant wordt de rol voor de duidelijkheid verdeeld over alle services en wordt de tolerantie minder robuust.

Elke service is niet alleen verantwoordelijk voor de tolerantie van de werking, maar ook voor de werkstroom. Deze verantwoordelijkheid kan lastig zijn voor de service en moeilijk te implementeren. Elke service moet tijdelijke, niet-tijdelijke en time-outfouten opnieuw proberen uit te proberen, zodat de aanvraag indien nodig zonder problemen wordt beëindigd. Daarnaast moet de service zorgvuldig te werk gaan om het slagen of mislukken van de bewerking te communiceren, zodat andere services dienovereenkomstig kunnen handelen.

Voorbeeld

In dit voorbeeld ziet u het patroon met de Drone Delivery-app. Wanneer een client een ophaalverzoek indient, wijst de app een drone toe en waarschuwt de client.

GitHub logo Een voorbeeld van dit patroon is beschikbaar op GitHub.

Een close-up van een kaartbeschrijving die automatisch wordt gegenereerd

Voor één zakelijke clienttransactie zijn drie verschillende bedrijfsactiviteiten vereist: het maken of bijwerken van een pakket, het toewijzen van een drone om het pakket te leveren en het controleren van de leveringsstatus. Deze bewerkingen worden uitgevoerd door drie microservices: Pakket, Drone Scheduler en Leveringsservices. In plaats van een centrale orchestrator gebruiken de services berichten om samen te werken en de aanvraag onderling te coördineren.

Ontwerp

De zakelijke transactie wordt in een reeks verwerkt via meerdere hops. Elke hop heeft een berichtenbus en de respectieve bedrijfsservice.

Wanneer een client een bezorgingsaanvraag verzendt via een HTTP-eindpunt, ontvangt de opnameservice dit, wordt er een bewerkingsgebeurtenis gemaakt en naar een berichtenbus gestuurd. De bus roept de geabonneerde bedrijfsservice aan en verzendt de gebeurtenis in een POST-aanvraag. Bij ontvangst van de gebeurtenis kan de bedrijfsservice de bewerking voltooien met succes, mislukt of kan er een time-out voor de aanvraag worden uitgevoerd. Als dit lukt, reageert de service op de bus met de statuscode OK, wordt er een nieuwe bewerkingsgebeurtenis gemaakt en wordt deze naar de berichtenbus van de volgende hop gestuurd. In het geval van een fout of time-out rapporteert de service een fout door de BadRequest-code te verzenden naar de berichtenbus die de oorspronkelijke POST-aanvraag heeft verzonden. De berichtbus onderactiet de bewerking opnieuw op basis van een beleid voor opnieuw proberen. Nadat deze periode is verlopen, markeert de berichtenbus de mislukte bewerking en stopt verdere verwerking van de hele aanvraag.

Deze werkstroom wordt voortgezet totdat de volledige aanvraag is verwerkt.

In het ontwerp worden meerdere berichtenbusjes gebruikt om de hele zakelijke transactie te verwerken. Microsoft Azure Event Grid biedt de berichtenservice. De app wordt geïmplementeerd in een AKS Azure Kubernetes Service cluster met twee containers in dezelfde pod. De ene container voert de ambassadeur uit die communiceert Event Grid de andere een bedrijfsservice. De aanpak met twee containers in dezelfde pod verbetert de prestaties en schaalbaarheid. De ambassadeur en de bedrijfsservice delen hetzelfde netwerk voor lage latentie en hoge doorvoer.

Om te voorkomen dat nieuwe pogingen die tot meerdere inspanningen kunnen leiden, trapsge keer worden Event Grid een bewerking opnieuw uitgevoerd in plaats van de bedrijfsservice. Er wordt een mislukte aanvraag gevlagd door een bericht te verzenden naar een wachtrij voor in uitgevallen berichten (DLQ).

De bedrijfsservices zijn idempotent om ervoor te zorgen dat nieuwe bewerkingen niet resulteren in dubbele resources. De Package-service maakt bijvoorbeeld gebruik van upsert-bewerkingen om gegevens toe te voegen aan het gegevensopslag.

In het voorbeeld wordt een aangepaste oplossing geïmplementeerd om aanroepen te correleren tussen alle services en Event Grid hops.

Hier ziet u een codevoorbeeld met het patroon tussen alle zakelijke services. U ziet de werkstroom van de droneleverings-app-transacties. Code voor afhandeling van uitzonderingen en logboekregistratie is voor de beknoptheid verwijderd.

[HttpPost]
[Route("/api/[controller]/operation")]
[ProducesResponseType(typeof(void), 200)]
[ProducesResponseType(typeof(void), 400)]
[ProducesResponseType(typeof(void), 500)]

public async Task<IActionResult> Post([FromBody] EventGridEvent[] events)
{

   if (events == null)
   {
       return BadRequest("No Event for Choreography");
   }

   foreach(var e in events)
   {

        List<EventGridEvent> listEvents = new List<EventGridEvent>();
        e.Topic = eventRepository.GetTopic();
        e.EventTime = DateTime.Now;
        switch (e.EventType)
        {
            case Operations.ChoreographyOperation.ScheduleDelivery:
            {
                var packageGen = await packageServiceCaller.UpsertPackageAsync(delivery.PackageInfo).ConfigureAwait(false);
                if (packageGen is null)
                {
                    //BadRequest allows the event to be reprocessed by Event Grid
                    return BadRequest("Package creation failed.");
                }

                //we set the event type to the next choreography step
                e.EventType = Operations.ChoreographyOperation.CreatePackage;
                listEvents.Add(e);
                await eventRepository.SendEventAsync(listEvents);
                return Ok("Created Package Completed");
            }
            case Operations.ChoreographyOperation.CreatePackage:
            {
                var droneId = await droneSchedulerServiceCaller.GetDroneIdAsync(delivery).ConfigureAwait(false);
                if (droneId is null)
                {
                    //BadRequest allows the event to be reprocessed by Event Grid
                    return BadRequest("could not get a drone id");
                }
                e.Subject = droneId;
                e.EventType = Operations.ChoreographyOperation.GetDrone;
                listEvents.Add(e);
                await eventRepository.SendEventAsync(listEvents);
                return Ok("Drone Completed");
            }
            case Operations.ChoreographyOperation.GetDrone:
            {
                var deliverySchedule = await deliveryServiceCaller.ScheduleDeliveryAsync(delivery, e.Subject);
                return Ok("Delivery Completed");
            }
            return BadRequest();
    }
}

Houd rekening met deze patronen in uw ontwerp voor de duidelijkheid.