Dela via


Implementera händelsebaserad kommunikation mellan mikrotjänster (integrationshändelser)

Dricks

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.

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

Som tidigare beskrivits publicerar en mikrotjänst en händelse när något anmärkningsvärt inträffar, till exempel när en affärsentitet uppdateras, när du använder händelsebaserad kommunikation. Andra mikrotjänster prenumererar på dessa händelser. När en mikrotjänst tar emot en händelse kan den uppdatera sina egna affärsentiteter, vilket kan leda till att fler händelser publiceras. Detta är kärnan i det slutliga konsekvenskonceptet. Det här publicerings-/prenumerationssystemet utförs vanligtvis med hjälp av en implementering av en händelsebuss. Händelsebussen kan utformas som ett gränssnitt med det API som behövs för att prenumerera och avbryta prenumerationen på händelser och publicera händelser. Den kan också ha en eller flera implementeringar baserat på kommunikation mellan processer eller meddelanden, till exempel en meddelandekö eller en servicebuss som stöder asynkron kommunikation och en modell för publicering/prenumeration.

Du kan använda händelser för att implementera affärstransaktioner som omfattar flera tjänster, vilket ger dig slutlig konsekvens mellan dessa tjänster. En så småningom konsekvent transaktion består av en serie distribuerade åtgärder. Vid varje åtgärd uppdaterar mikrotjänsten en affärsentitet och publicerar en händelse som utlöser nästa åtgärd. Tänk på att transaktionen inte omfattar den underliggande beständigheten och händelsebussen, så idempotens måste hanteras. Bild 6–18 nedan visar en PriceUpdated-händelse som publicerats via en händelsebuss, så prisuppdateringen sprids till korgen och andra mikrotjänster.

Diagram of asynchronous event-driven communication with an event bus.

Bild 6-18. Händelsedriven kommunikation baserad på en händelsebuss

I det här avsnittet beskrivs hur du kan implementera den här typen av kommunikation med .NET med hjälp av ett allmänt event bus-gränssnitt, enligt bild 6–18. Det finns flera potentiella implementeringar, där var och en använder en annan teknik eller infrastruktur, till exempel RabbitMQ, Azure Service Bus eller någon annan öppen källkod eller kommersiell servicebuss från tredje part.

Använda meddelandeköer och servicebussar för produktionssystem

Som du ser i arkitekturavsnittet kan du välja mellan flera meddelandetekniker för att implementera din abstrakta händelsebuss. Men dessa tekniker är på olika nivåer. Till exempel är RabbitMQ, en meddelandekoordinatortransport, på en lägre nivå än kommersiella produkter som Azure Service Bus, NServiceBus, MassTransit eller Brighter. De flesta av dessa produkter kan fungera ovanpå antingen RabbitMQ eller Azure Service Bus. Ditt val av produkt beror på hur många funktioner och hur mycket out-of-the-box skalbarhet du behöver för ditt program.

För att bara implementera ett konceptbevis för event bus för din utvecklingsmiljö, som i exemplet eShopOnContainers, kan en enkel implementering ovanpå RabbitMQ som körs som en container räcka. Men för verksamhetskritiska system och produktionssystem som behöver hög skalbarhet kanske du vill utvärdera och använda Azure Service Bus.

Om du behöver abstraktioner på hög nivå och rikare funktioner som Sagas för långvariga processer som gör distribuerad utveckling enklare, är andra kommersiella servicebussar och servicebussar med öppen källkod som NServiceBus, MassTransit och Brighter värda att utvärdera. I det här fallet skulle abstraktionerna och API:et som ska användas vanligtvis vara direkt de som tillhandahålls av dessa högnivåbussar i stället för dina egna abstraktioner (som de enkla händelsebussabstraktionerna som tillhandahålls på eShopOnContainers). För den delen kan du undersöka förgrenade eShopOnContainers med hjälp av NServiceBus (ytterligare härledda exempel som implementeras av Särskild programvara).

Naturligtvis kan du alltid skapa dina egna service bus-funktioner ovanpå tekniker på lägre nivå som RabbitMQ och Docker, men det arbete som krävs för att "återuppfinna hjulet" kan vara för dyrt för ett anpassat företagsprogram.

För att upprepa: de exempel på abstraktioner och implementering av händelsebussar som visas i exemplet eShopOnContainers är avsedda att endast användas som ett konceptbevis. När du har bestämt dig för att du vill ha asynkron och händelsedriven kommunikation, enligt beskrivningen i det aktuella avsnittet, bör du välja den Service Bus-produkt som bäst passar dina behov för produktion.

Integrationshändelser

Integreringshändelser används för att synkronisera domäntillstånd över flera mikrotjänster eller externa system. Den här funktionen görs genom att publicera integrationshändelser utanför mikrotjänsten. När en händelse publiceras till flera mottagarmikrotjänster (till så många mikrotjänster som prenumererar på integrationshändelsen) hanterar lämplig händelsehanterare i varje mottagarmikrotjänst händelsen.

En integrationshändelse är i princip en datahållningsklass, som i följande exempel:

public class ProductPriceChangedIntegrationEvent : IntegrationEvent
{
    public int ProductId { get; private set; }
    public decimal NewPrice { get; private set; }
    public decimal OldPrice { get; private set; }

    public ProductPriceChangedIntegrationEvent(int productId, decimal newPrice,
        decimal oldPrice)
    {
        ProductId = productId;
        NewPrice = newPrice;
        OldPrice = oldPrice;
    }
}

Integreringshändelserna kan definieras på programnivå för varje mikrotjänst, så de är frikopplade från andra mikrotjänster, på ett sätt som är jämförbart med hur ViewModels definieras på servern och klienten. Vad som inte rekommenderas är att dela ett gemensamt bibliotek för integreringshändelser mellan flera mikrotjänster. Detta skulle vara att koppla dessa mikrotjänster till ett databibliotek för en enskild händelsedefinition. Du vill inte göra det av samma skäl som du inte vill dela en gemensam domänmodell för flera mikrotjänster: mikrotjänster måste vara helt autonoma. Mer information finns i det här blogginlägget om mängden data som ska placeras i händelser. Var noga med att inte ta detta för långt, eftersom det andra blogginlägget beskriver problemdata som bristfälliga meddelanden kan producera. Din utformning av dina evenemang bör syfta till att vara "helt rätt" för sina konsumenters behov.

Det finns bara några olika typer av bibliotek som du bör dela mellan mikrotjänster. Det ena är bibliotek som är sista programblock, till exempel Event Bus-klient-API:et, som i eShopOnContainers. Ett annat är bibliotek som utgör verktyg som också kan delas som NuGet-komponenter, till exempel JSON-serialiserare.

Händelsebussen

En händelsebuss tillåter kommunikation i publicerings-/prenumerationsstil mellan mikrotjänster utan att komponenterna uttryckligen måste känna till varandra, enligt bild 6–19.

A diagram showing the basic publish/subscribe pattern.

Bild 6-19. Publicera/prenumerera på grunderna med en händelsebuss

Diagrammet ovan visar att mikrotjänst A publicerar till Event Bus, som distribueras till prenumererande mikrotjänster B och C, utan att utgivaren behöver känna prenumeranterna. Händelsebussen är relaterad till observer-mönstret och publiceringsprenumereringsmönstret.

Observatörsmönster

I mönstret Observatör meddelar ditt primära objekt (kallas observerbart) andra intresserade objekt (kallas observatörer) med relevant information (händelser).

Publicera/prenumerera (pub/under) mönster

Syftet med mönstret Publicera/prenumerera är detsamma som mönstret Observatör: du vill meddela andra tjänster när vissa händelser äger rum. Men det finns en viktig skillnad mellan mönstret Observer och Pub/Sub. I observatörsmönstret utförs sändningen direkt från det observerbara till observatörerna, så att de "känner" varandra. Men när du använder ett Pub/Sub-mönster finns det en tredje komponent, som kallas asynkron meddelandekö eller händelsebuss, som är känd av både utgivaren och prenumeranten. När du använder Pub/Sub-mönstret är utgivaren och prenumeranterna därför exakt frikopplade tack vare den nämnda händelsebussen eller meddelandekoordinatorn.

Mellanhanden eller händelsebussen

Hur uppnår du anonymitet mellan utgivare och prenumerant? Ett enkelt sätt är att låta en mellanhand ta hand om all kommunikation. En händelsebuss är en sådan mellanhand.

En händelsebuss består vanligtvis av två delar:

  • Abstraktionen eller gränssnittet.

  • En eller flera implementeringar.

I bild 6–19 kan du se hur händelsebussen ur programsynpunkt inte är något annat än en pub-/underkanal. Hur du implementerar den här asynkrona kommunikationen kan variera. Det kan ha flera implementeringar så att du kan växla mellan dem, beroende på miljökraven (till exempel produktions- och utvecklingsmiljöer).

I bild 6–20 kan du se en abstraktion av en händelsebuss med flera implementeringar baserade på infrastrukturmeddelandetekniker som RabbitMQ, Azure Service Bus eller en annan händelse-/meddelandekoordinator.

Diagram showing the addition of an event bus abstraction layer.

Bild 6- 20. Flera implementeringar av en händelsebuss

Det är bra att ha händelsebussen definierad via ett gränssnitt så att den kan implementeras med flera tekniker, till exempel RabbitMQ, Azure Service Bus eller andra. Men som tidigare nämnts är det bara bra att använda egna abstraktioner (event bus-gränssnittet) om du behöver grundläggande event bus-funktioner som stöds av dina abstraktioner. Om du behöver mer omfattande service bus-funktioner bör du förmodligen använda API:et och abstraktionerna som tillhandahålls av din föredragna kommersiella servicebuss i stället för dina egna abstraktioner.

Definiera ett event bus-gränssnitt

Vi börjar med lite implementeringskod för event bus-gränssnittet och möjliga implementeringar i utforskningssyfte. Gränssnittet ska vara allmänt och enkelt, som i följande gränssnitt.

public interface IEventBus
{
    void Publish(IntegrationEvent @event);

    void Subscribe<T, TH>()
        where T : IntegrationEvent
        where TH : IIntegrationEventHandler<T>;

    void SubscribeDynamic<TH>(string eventName)
        where TH : IDynamicIntegrationEventHandler;

    void UnsubscribeDynamic<TH>(string eventName)
        where TH : IDynamicIntegrationEventHandler;

    void Unsubscribe<T, TH>()
        where TH : IIntegrationEventHandler<T>
        where T : IntegrationEvent;
}

Metoden Publish är enkel. Händelsebussen sänder integrationshändelsen som skickas till den till alla mikrotjänster, eller till och med ett externt program, som prenumererar på händelsen. Den här metoden används av den mikrotjänst som publicerar händelsen.

Metoderna Subscribe (du kan ha flera implementeringar beroende på argumenten) används av de mikrotjänster som vill ta emot händelser. Den här metoden har två argument. Den första är integrationshändelsen som du prenumererar på (IntegrationEvent). Det andra argumentet är integrationshändelsehanteraren (eller återanropsmetoden), med namnet IIntegrationEventHandler<T>, som ska köras när mottagarens mikrotjänst hämtar meddelandet om integrationshändelsen.

Ytterligare resurser

Några produktionsklara meddelandelösningar: