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
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:
Open
SendReceiveWithNservicebus.sln
in uw favoriete code-editor (bijvoorbeeld Visual Studio 2022).Open
appsettings.json
zowel de projecten Ontvanger als Afzender en stel deze inAzureServiceBusConnectionString
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.cs
configureert 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 Ping
ontvangt, 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:
- Klik met de rechtermuisknop op de oplossing in Solution Explorer
- Selecteer 'Opstartprojecten instellen...'
- Meerdere opstartprojecten selecteren
- 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:
- Openen
Program.cs
in het project Afzender - 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:
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:
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
.
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:
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: