Delen via


Berichtgestuurde bedrijfstoepassingen bouwen met NServiceBus en Azure Service Bus

NServiceBus is een commercieel berichtenframework dat wordt geleverd door Particular Software. Het is gebouwd op Basis van Azure Service Bus en helpt ontwikkelaars zich te concentreren op bedrijfslogica door problemen met de infrastructuur te abstraheren. In deze handleiding bouwen we een oplossing waarmee berichten tussen twee services worden uitgewisseld. We laten ook zien hoe u automatisch mislukte berichten opnieuw probeert en opties bekijkt voor het hosten van deze services in Azure.

Notitie

De code voor deze zelfstudie is beschikbaar op de website Van Specifieke Software Docs.

Vereisten

In het voorbeeld wordt ervan uitgegaan dat u een Azure Service Bus-naamruimte hebt gemaakt.

Belangrijk

NServiceBus vereist ten minste de Standard-laag. De Basic-laag werkt niet.

De oplossing downloaden en voorbereiden

  1. Download de code van de specifieke Software Docs-website. De oplossing SendReceiveWithNservicebus.sln bestaat uit drie projecten:

    • Afzender: een consoletoepassing waarmee berichten worden verzonden
    • Ontvanger: een consoletoepassing die berichten ontvangt van de afzender en antwoorden terug
    • Gedeeld: een klassebibliotheek met de berichtcontracten die worden gedeeld tussen de afzender en ontvanger

    Het volgende diagram, gegenereerd door ServiceInsight, een hulpprogramma voor visualisaties en foutopsporing van bepaalde software, toont de berichtstroom:

    Afbeelding van het sequentiediagram

  2. Open SendReceiveWithNservicebus.sln in uw favoriete code-editor (bijvoorbeeld Visual Studio 2022).

  3. Open appsettings.json zowel de projecten Ontvanger als Afzender en stel deze in AzureServiceBusConnectionString op de verbindingsreeks voor uw Azure Service Bus-naamruimte.

    • Dit is te vinden in Azure Portal onder Instellingen>>voor gedeelde toegangsinstellingen voor gedeeld toegangsbeleid>rootManageSharedAccessKey>primaire verbindingsreeks.
    • De AzureServiceBusTransport constructor heeft ook een constructor die een naamruimte en tokenreferentie accepteert, die in een productieomgeving veiliger is, maar voor de doeleinden van deze zelfstudie wordt de gedeelde toegangssleutel verbindingsreeks gebruikt.

De gedeelde berichtcontracten definiëren

In de gedeelde klassebibliotheek definieert u de contracten die worden gebruikt om onze berichten te verzenden. Het bevat een verwijzing naar het NServiceBus NuGet-pakket, dat interfaces bevat die u kunt gebruiken om onze berichten te identificeren. De interfaces zijn niet vereist, maar ze geven ons extra validatie van NServiceBus en zorgen ervoor dat de code zelfdocumentatie kan uitvoeren.

Eerst bekijken we de Ping.cs klas

public class Ping : NServiceBus.ICommand
{
    public int Round { get; set; }
}

De Ping klasse definieert een bericht dat de afzender naar de ontvanger verzendt. Het is een eenvoudige C#-klasse die implementeert NServiceBus.ICommand, een interface van het NServiceBus-pakket. Dit bericht is een signaal voor de lezer en NServiceBus dat het een opdracht is, hoewel er andere manieren zijn om berichten te identificeren zonder interfaces te gebruiken.

De andere berichtklasse in de gedeelde projecten is Pong.cs:

public class Pong : NServiceBus.IMessage
{
    public string Acknowledgement { get; set; }
}

Pong is ook een eenvoudig C#-object, hoewel deze wordt geïmplementeerd NServiceBus.IMessage. De IMessage interface vertegenwoordigt een algemeen bericht dat geen opdracht of gebeurtenis is en vaak wordt gebruikt voor antwoorden. In ons voorbeeld is het een antwoord dat de ontvanger terugstuurt naar de afzender om aan te geven dat er een bericht is ontvangen.

De Ping en Pong zijn de twee berichttypen die u gaat gebruiken. De volgende stap is het configureren van de afzender voor het gebruik van Azure Service Bus en het verzenden van een Ping bericht.

De afzender instellen

De afzender is een eindpunt dat ons Ping bericht verzendt. Hier configureert u de afzender voor het gebruik van Azure Service Bus als transportmechanisme, maakt u vervolgens een Ping exemplaar en verzendt u deze.

In de Main methode van Program.csconfigureert u het eindpunt afzender:

var host = Host.CreateDefaultBuilder(args)
    // Configure a host for the endpoint
    .ConfigureLogging((context, logging) =>
    {
        logging.AddConfiguration(context.Configuration.GetSection("Logging"));

        logging.AddConsole();
    })
    .UseConsoleLifetime()
    .UseNServiceBus(context =>
    {
        // Configure the NServiceBus endpoint
        var endpointConfiguration = new EndpointConfiguration("Sender");

        var connectionString = context.Configuration.GetConnectionString("AzureServiceBusConnectionString");
        // If token credentials are to be used, the overload constructor for AzureServiceBusTransport would be used here
        var routing = endpointConfiguration.UseTransport(new AzureServiceBusTransport(connectionString));
        endpointConfiguration.UseSerialization<SystemJsonSerializer>();

        endpointConfiguration.AuditProcessedMessagesTo("audit");
        routing.RouteToEndpoint(typeof(Ping), "Receiver");

        endpointConfiguration.EnableInstallers();

        return endpointConfiguration;
    })
    .ConfigureServices(services => services.AddHostedService<SenderWorker>())
    .Build();

await host.RunAsync();

Er is veel om hier uit te pakken, dus we bekijken het stap voor stap.

Een host voor het eindpunt configureren

Hosting en logboekregistratie worden geconfigureerd met standaardopties voor Microsoft Generic Host. Op dit moment is het eindpunt geconfigureerd om te worden uitgevoerd als een consoletoepassing, maar het kan worden gewijzigd om te worden uitgevoerd in Azure Functions met minimale wijzigingen, die verderop in dit artikel worden besproken.

Het NServiceBus-eindpunt configureren

Vervolgens laat u de host weten NServiceBus te gebruiken met de .UseNServiceBus(…) extensiemethode. De methode gebruikt een callback-functie die een eindpunt retourneert dat wordt gestart wanneer de host wordt uitgevoerd.

In de eindpuntconfiguratie geeft AzureServiceBus u op voor ons transport en geeft u een verbindingsreeks van appsettings.json. Vervolgens stelt u de routering in, zodat berichten van het type Ping worden verzonden naar een eindpunt met de naam Ontvanger. Hiermee kan NServiceBus het proces van het verzenden van het bericht naar de bestemming automatiseren zonder dat het adres van de ontvanger is vereist.

De aanroep voor EnableInstallers het instellen van de topologie in de Azure Service Bus-naamruimte wanneer het eindpunt wordt gestart, waardoor de vereiste wachtrijen waar nodig worden gemaakt. In productieomgevingen is operationele scripting een andere optie om de topologie te maken.

Achtergrondservice instellen voor het verzenden van berichten

Het laatste deel van de afzender is SenderWorker, een achtergrondservice die is geconfigureerd om elke seconde een Ping bericht te verzenden.

public class SenderWorker : BackgroundService
{
    private readonly IMessageSession messageSession;
    private readonly ILogger<SenderWorker> logger;

    public SenderWorker(IMessageSession messageSession, ILogger<SenderWorker> logger)
    {
        this.messageSession = messageSession;
        this.logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            var round = 0;
            while (!stoppingToken.IsCancellationRequested)
            {
                await messageSession.Send(new Ping { Round = round++ });;

                logger.LogInformation($"Message #{round}");

                await Task.Delay(1_000, stoppingToken);
            }
        }
        catch (OperationCanceledException)
        {
            // graceful shutdown
        }
    }
}

De IMessageSession gebruikte in ExecuteAsync wordt opgenomen in SenderWorker en stelt ons in staat om berichten te verzenden met behulp van NServiceBus buiten een berichtenhandler. Met de routering die u hebt Sender geconfigureerd, wordt de bestemming van de Ping berichten opgegeven. Het houdt de topologie van het systeem (welke berichten worden doorgestuurd naar welke adressen) als een afzonderlijke zorg van de bedrijfscode.

De afzendertoepassing bevat ook een PongHandler. U gaat terug naar de ontvanger nadat we de ontvanger hebben besproken, die we hierna gaan doen.

De ontvanger instellen

De ontvanger is een eindpunt dat luistert naar een Ping bericht, registreert wanneer een bericht wordt ontvangen en antwoordt op de afzender. In deze sectie bekijken we snel de eindpuntconfiguratie, die vergelijkbaar is met de afzender, en richten we onze aandacht vervolgens op de berichthandler.

Net als de afzender stelt u de ontvanger in als een consoletoepassing met behulp van de Microsoft Generic Host. Deze maakt gebruik van dezelfde configuratie voor logboekregistratie en eindpunten (met Azure Service Bus als het berichttransport), maar met een andere naam om deze te onderscheiden van de afzender:

var endpointConfiguration = new EndpointConfiguration("Receiver");

Omdat dit eindpunt alleen antwoordt op de oorspronkelijke afzender en geen nieuwe gesprekken start, is er geen routeringsconfiguratie vereist. Er is ook geen achtergrondmedewerker nodig, zoals de afzender, omdat deze alleen antwoordt wanneer er een bericht wordt ontvangen.

De Ping-berichthandler

Het ontvangerproject bevat een berichthandler met de naamPingHandler:

public class PingHandler : NServiceBus.IHandleMessages<Ping>
{
    private readonly ILogger<PingHandler> logger;

    public PingHandler(ILogger<PingHandler> logger)
    {
        this.logger = logger;
    }

    public async Task Handle(Ping message, IMessageHandlerContext context)
    {
        logger.LogInformation($"Processing Ping message #{message.Round}");

        // throw new Exception("BOOM");

        var reply = new Pong { Acknowledgement = $"Ping #{message.Round} processed at {DateTimeOffset.UtcNow:s}" };

        await context.Reply(reply);
    }
}

Laten we de commentaarcode voorlopig negeren. We gaan later terug als we het hebben over het herstellen van een fout.

De klasse implementeert IHandleMessages<Ping>, waarmee één methode wordt gedefinieerd: Handle. Deze interface vertelt NServiceBus dat wanneer het eindpunt een bericht van het type Pingontvangt, het moet worden verwerkt door de Handle methode in deze handler. De Handle methode gebruikt het bericht zelf als een parameter en een IMessageHandlerContext, waarmee verdere berichtenbewerkingen worden toegestaan, zoals beantwoorden, verzenden van opdrachten of het publiceren van gebeurtenissen.

Ons PingHandler is eenvoudig: wanneer een Ping bericht wordt ontvangen, kunt u de berichtgegevens vastleggen en de afzender beantwoorden met een nieuw Pong bericht, dat vervolgens wordt verwerkt in de afzender PongHandler.

Notitie

In de configuratie van de afzender hebt u opgegeven dat Ping berichten moeten worden doorgestuurd naar de ontvanger. NServiceBus voegt metagegevens toe aan de berichten die onder andere de oorsprong van het bericht aangeven. Daarom hoeft u geen routeringsgegevens voor het Pong antwoordbericht op te geven. Het bericht wordt automatisch teruggeleid naar de oorsprong ervan: de afzender.

Nu de afzender en ontvanger beide goed zijn geconfigureerd, kunt u de oplossing nu uitvoeren.

De oplossing uitvoeren

Als u de oplossing wilt starten, moet u zowel de afzender als de ontvanger uitvoeren. Als u Visual Studio Code gebruikt, start u de configuratie 'Alle foutopsporing'. Als u Visual Studio gebruikt, configureert u de oplossing om zowel de projecten Afzender als Ontvanger te starten:

  1. Klik met de rechtermuisknop op de oplossing in Solution Explorer
  2. Selecteer 'Opstartprojecten instellen...'
  3. Meerdere opstartprojecten selecteren
  4. Voor zowel de afzender als de ontvanger selecteert u 'Start' in de vervolgkeuzelijst

Start de oplossing. Er worden twee consoletoepassingen weergegeven, één voor de afzender en één voor de ontvanger.

In de afzender ziet u dat er elke seconde een Ping bericht wordt verzonden, dankzij de SenderWorker achtergrondtaak. De ontvanger geeft de details weer van elk Ping bericht dat wordt ontvangen en de afzender registreert de details van elk Pong bericht dat het ontvangt in antwoord.

Nu alles werkt, laten we het breken.

Tolerantie in actie

Fouten zijn een feit van het leven in softwaresystemen. Het is onvermijdelijk dat code mislukt en dit kan om verschillende redenen, zoals netwerkfouten, databasevergrendelingen, wijzigingen in een API van derden en gewone oude coderingsfouten.

NServiceBus heeft robuuste herstelmogelijkheden voor het afhandelen van fouten. Wanneer een berichtenhandler mislukt, worden berichten automatisch opnieuw geprobeerd op basis van een vooraf gedefinieerd beleid. Er zijn twee soorten beleid voor opnieuw proberen: onmiddellijke nieuwe pogingen en vertraagde nieuwe pogingen. De beste manier om te beschrijven hoe ze werken, is door ze in actie te zien. Laten we een beleid voor opnieuw proberen toevoegen aan het eindpunt van de ontvanger:

  1. Openen Program.cs in het project Afzender
  2. Voeg na de .EnableInstallers regel de volgende code toe:
endpointConfiguration.SendFailedMessagesTo("error");
var recoverability = endpointConfiguration.Recoverability();
recoverability.Immediate(
    immediate =>
    {
        immediate.NumberOfRetries(3);
    });
recoverability.Delayed(
    delayed =>
    {
        delayed.NumberOfRetries(2);
        delayed.TimeIncrease(TimeSpan.FromSeconds(5));
    });

Voordat we bespreken hoe dit beleid werkt, gaan we het in actie zien. Voordat u het herstelbeleid test, moet u een fout simuleren. Open de PingHandler code in het ontvangerproject en verwijder commentaar op deze regel:

throw new Exception("BOOM");

Wanneer de ontvanger een Ping bericht verwerkt, mislukt het. Start de oplossing opnieuw en laten we eens kijken wat er gebeurt in de ontvanger.

Met onze minder betrouwbare PingHandler, mislukken al onze berichten. U ziet dat het beleid voor opnieuw proberen wordt gestart voor deze berichten. De eerste keer dat een bericht mislukt, wordt het maximaal drie keer opnieuw geprobeerd:

Afbeelding van het beleid voor onmiddellijke nieuwe pogingen waarmee berichten maximaal 3 keer opnieuw worden geprobeerd

Het blijft natuurlijk mislukken, dus wanneer de drie onmiddellijke nieuwe pogingen worden gebruikt, wordt het vertraagde beleid voor opnieuw proberen geactiveerd en wordt het bericht vijf seconden vertraagd:

Afbeelding van het vertraagde beleid voor opnieuw proberen dat de berichten in stappen van 5 seconden vertraagt voordat een andere ronde van onmiddellijke nieuwe pogingen wordt uitgevoerd

Nadat deze vijf seconden zijn verstreken, wordt het bericht opnieuw drie keer opnieuw geprobeerd (dat wil gezegd, een andere iteratie van het beleid voor onmiddellijke nieuwe pogingen). Deze mislukken ook en NServiceBus vertraagt het bericht opnieuw, deze keer gedurende 10 seconden, voordat u het opnieuw probeert.

Als PingHandler het nog steeds niet lukt nadat het volledige beleid voor opnieuw proberen is uitgevoerd, wordt het bericht geplaatst in een gecentraliseerde foutwachtrij met de naam error, zoals gedefinieerd door de aanroep naar SendFailedMessagesTo.

Afbeelding van het mislukte bericht

Het concept van een gecentraliseerde foutwachtrij verschilt van het mechanisme voor dode letters in Azure Service Bus, dat een wachtrij met dode letters heeft voor elke verwerkingswachtrij. Met NServiceBus fungeren de wachtrijen met dode letters in Azure Service Bus als waar gif-berichtenwachtrijen, terwijl berichten die uiteindelijk in de gecentraliseerde foutwachtrij terechtkomen, op een later tijdstip opnieuw kunnen worden verwerkt, indien nodig.

Het beleid voor opnieuw proberen helpt bij het oplossen van verschillende typen fouten die vaak tijdelijk of semi-tijdelijk van aard zijn. Dat wil gezegd, fouten die tijdelijk zijn en vaak verdwijnen als het bericht na een korte vertraging opnieuw wordt verwerkt. Voorbeelden hiervan zijn netwerkfouten, databasevergrendelingen en api-storingen van derden.

Zodra een bericht zich in de foutwachtrij bevindt, kunt u de berichtdetails in het hulpprogramma van uw keuze bekijken en vervolgens bepalen wat u ermee moet doen. Als u bijvoorbeeld ServicePulse gebruikt, een bewakingsprogramma van bepaalde software, kunnen we de berichtdetails en de reden voor de fout bekijken:

Afbeelding van ServicePulse, van bepaalde software

Nadat u de details hebt bekeken, kunt u het bericht terugsturen naar de oorspronkelijke wachtrij voor verwerking. U kunt het bericht ook bewerken voordat u dit doet. Als er meerdere berichten in de foutwachtrij staan, die om dezelfde reden is mislukt, kunnen ze allemaal als batch worden teruggestuurd naar hun oorspronkelijke bestemmingen.

Vervolgens is het tijd om erachter te komen waar onze oplossing in Azure moet worden geïmplementeerd.

Waar u de services in Azure kunt hosten

In dit voorbeeld zijn de eindpunten voor afzender en ontvanger geconfigureerd om te worden uitgevoerd als consoletoepassingen. Ze kunnen ook worden gehost in verschillende Azure-services, waaronder Azure Functions, Azure-app Services, Azure Container Instances, Azure Kubernetes Services en Azure-VM's. Hier ziet u bijvoorbeeld hoe het eindpunt van de afzender kan worden geconfigureerd om te worden uitgevoerd als een Azure-functie:

[assembly: NServiceBusTriggerFunction("Sender")]
public class Program
{
    public static async Task Main()
    {
        var host = new HostBuilder()
            .ConfigureFunctionsWorkerDefaults()
            .UseNServiceBus(configuration =>
            {
                configuration.Routing().RouteToEndpoint(typeof(Ping), "Receiver");
            })
            .Build();

        await host.RunAsync();
    }
}

Zie Azure Functions met Azure Service Bus in de documentatie van NServiceBus voor meer informatie over het gebruik van NServiceBus met Functions.

Volgende stappen

Zie de volgende artikelen voor meer informatie over het gebruik van NServiceBus met Azure-services: