Tworzenie aplikacji biznesowych opartych na komunikatach przy użyciu sieci NServiceBus i Azure Service Bus

NServiceBus to komercyjna platforma obsługi komunikatów dostarczana przez określone oprogramowanie. Jest ona oparta na Azure Service Bus i pomaga deweloperom skupić się na logice biznesowej przez abstrakcję problemów z infrastrukturą. W tym przewodniku utworzymy rozwiązanie, które wymienia komunikaty między dwiema usługami. Pokażemy również, jak automatycznie ponowić próbę niepowodzenia komunikatów i przejrzeć opcje hostowania tych usług na platformie Azure.

Wymagania wstępne

W przykładzie założono, że utworzono Azure Service Bus przestrzeni nazw.

Ważne

NServiceBus wymaga co najmniej warstwy Standardowa. Warstwa Podstawowa nie będzie działać.

Pobieranie i przygotowywanie rozwiązania

  1. Pobierz kod z witryny sieci Web Docs określonego oprogramowania. Rozwiązanie SendReceiveWithNservicebus.sln składa się z trzech projektów:

    • Nadawca: aplikacja konsolowa, która wysyła komunikaty
    • Odbiornik: aplikacja konsolowa, która odbiera komunikaty od nadawcy i odpowiada z powrotem
    • Udostępnione: biblioteka klas zawierająca kontrakty komunikatów współużytkowane przez nadawcę i odbiorcę

    Na poniższym diagramie wygenerowanym przez usługę ServiceInsight, narzędziu do wizualizacji i debugowania z określonego oprogramowania przedstawiono przepływ komunikatów:

    Obraz przedstawiający diagram sekwencji

  2. Otwórz plik SendReceiveWithNservicebus.sln w ulubionym edytorze kodu (na przykład Visual Studio 2019).

  3. Otwórz appsettings.json plik zarówno w projektach Receiver, jak i Sender, a następnie ustaw AzureServiceBusConnectionString parametry połączenia dla przestrzeni nazw Azure Service Bus.

Definiowanie kontraktów komunikatów udostępnionych

Biblioteka klas udostępnionych służy do definiowania kontraktów używanych do wysyłania naszych komunikatów. Zawiera odwołanie do NServiceBus pakietu NuGet, który zawiera interfejsy, których można użyć do identyfikowania naszych komunikatów. Interfejsy nie są wymagane, ale zapewniają nam dodatkową walidację z NServiceBus i umożliwiają samodzielne dokumentowanie kodu.

Najpierw przejrzymy klasę Ping.cs

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

Klasa Ping definiuje komunikat, który nadawca wysyła do odbiorcy. Jest to prosta klasa języka C#, która implementuje NServiceBus.ICommandinterfejs z pakietu NServiceBus. Ten komunikat jest sygnałem dla czytnika i NServiceBus, że jest to polecenie, chociaż istnieją inne sposoby identyfikowania komunikatów bez używania interfejsów.

Druga klasa komunikatów w projektach udostępnionych to Pong.cs:

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

Pong jest również prostym obiektem języka C#, chociaż ten obiekt implementuje NServiceBus.IMessageelement . Interfejs IMessage reprezentuje ogólny komunikat, który nie jest ani poleceniem, ani zdarzeniem, i jest często używany do odpowiedzi. W naszym przykładzie jest to odpowiedź, którą odbiorca wysyła z powrotem do nadawcy, aby wskazać, że wiadomość została odebrana.

Elementy Ping i Pong to dwa typy komunikatów, których będziesz używać. Następnym krokiem jest skonfigurowanie nadawcy do używania Azure Service Bus i wysyłania Ping wiadomości.

Konfigurowanie nadawcy

Nadawca jest punktem końcowym, który wysyła wiadomość Ping . W tym miejscu skonfigurujesz nadawcę do używania Azure Service Bus jako mechanizmu transportu, a następnie skonstruujesz Ping wystąpienie i wyślesz je.

W metodzie w metodzie MainProgram.csnależy skonfigurować punkt końcowy nadawcy:

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 transport = endpointConfiguration.UseTransport<AzureServiceBusTransport>();
        var connectionString = context.Configuration.GetConnectionString("AzureServiceBusConnectionString");
        transport.ConnectionString(connectionString);

        transport.Routing().RouteToEndpoint(typeof(Ping), "Receiver");

        endpointConfiguration.EnableInstallers();
        endpointConfiguration.AuditProcessedMessagesTo("audit");

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

await host.RunAsync();

Jest tu wiele do rozpakowania, więc zapoznamy się z nim krok po kroku.

Konfigurowanie hosta dla punktu końcowego

Hosting i rejestrowanie są konfigurowane przy użyciu standardowych opcji hosta ogólnego firmy Microsoft. Na razie punkt końcowy jest skonfigurowany do uruchamiania jako aplikacja konsolowa, ale można go zmodyfikować tak, aby był uruchamiany w Azure Functions z minimalnymi zmianami, które omówimy w dalszej części tego artykułu.

Konfigurowanie punktu końcowego NServiceBus

Następnie należy poinformować hosta, aby używał NServiceBus z .UseNServiceBus(…) metodą rozszerzenia. Metoda przyjmuje funkcję wywołania zwrotnego, która zwraca punkt końcowy, który zostanie uruchomiony po uruchomieniu hosta.

W konfiguracji punktu końcowego określisz AzureServiceBus dla naszego transportu parametry połączenia z appsettings.json. Następnie skonfigurujesz routing, aby komunikaty typu Ping zostały wysłane do punktu końcowego o nazwie "Receiver". Umożliwia NServiceBus zautomatyzowanie procesu wysyłania komunikatu do miejsca docelowego bez wymagania adresu odbiorcy.

Wywołanie metody EnableInstallers spowoduje skonfigurowanie naszej topologii w Azure Service Bus przestrzeni nazw po uruchomieniu punktu końcowego, tworząc wymagane kolejki w razie potrzeby. W środowiskach produkcyjnych skrypty operacyjne to inna opcja tworzenia topologii.

Konfigurowanie usługi w tle do wysyłania komunikatów

Ostatnim elementem nadawcy jest SenderWorkerusługa w tle skonfigurowana do wysyłania komunikatu Ping co sekundę.

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++ })
                    .ConfigureAwait(false);

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

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

Używany IMessageSession element jest ExecuteAsync wstrzykiwany do SenderWorker elementu i umożliwia nam wysyłanie komunikatów przy użyciu NServiceBus poza procedurą obsługi komunikatów. Routing skonfigurowany w programie Sender określa miejsce docelowe komunikatów Ping . Utrzymuje topologię systemu (do którego są kierowane komunikaty) jako osobny problem od kodu biznesowego.

Aplikacja Nadawca zawiera również element PongHandler. Wrócisz do niego po omówieniu odbiornika, który zrobimy dalej.

Konfigurowanie odbiornika

Odbiornik to punkt końcowy, który nasłuchuje komunikatu Ping , rejestruje po odebraniu komunikatu i odpowiada z powrotem do nadawcy. W tej sekcji szybko przejrzymy konfigurację punktu końcowego, która jest podobna do nadawcy, a następnie zwrócimy uwagę na procedurę obsługi komunikatów.

Podobnie jak nadawca, skonfiguruj odbiornik jako aplikację konsolową przy użyciu hosta ogólnego firmy Microsoft. Używa ona tej samej konfiguracji rejestrowania i punktu końcowego (z Azure Service Bus co transport komunikatów), ale z inną nazwą, aby odróżnić ją od nadawcy:

var endpointConfiguration = new EndpointConfiguration("Receiver");

Ponieważ ten punkt końcowy odpowiada tylko jego inicjatorowi i nie uruchamia nowych konwersacji, nie jest wymagana żadna konfiguracja routingu. Nie potrzebuje również procesu roboczego w tle, takiego jak nadawca, ponieważ odpowiada tylko wtedy, gdy otrzymuje komunikat.

Procedura obsługi komunikatów ping

Projekt Receiver zawiera program obsługi komunikatów o nazwie PingHandler:

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);
    }
}

Teraz zignorujmy kod skomentowany; Wrócimy do niego później, gdy będziemy mówić o odzyskiwaniu po awarii.

Klasa implementuje IHandleMessages<Ping>metodę , która definiuje jedną metodę: Handle. Ten interfejs informuje NServiceBus, że gdy punkt końcowy odbiera komunikat typu Ping, powinien zostać przetworzony przez metodę Handle w tej procedurze obsługi. Metoda Handle przyjmuje sam komunikat jako parametr i IMessageHandlerContext, który umożliwia dalsze operacje obsługi komunikatów, takie jak odpowiadanie, wysyłanie poleceń lub publikowanie zdarzeń.

Nasze PingHandler jest proste: po odebraniu Ping komunikatu zarejestruj szczegóły wiadomości i odpowiedz nadawcy przy użyciu nowej Pong wiadomości.

Uwaga

W konfiguracji nadawcy określono, że Ping komunikaty powinny być kierowane do odbiornika. NServiceBus dodaje metadane do komunikatów wskazujących między innymi źródło komunikatu. Dlatego nie trzeba określać żadnych danych routingu Pong dla wiadomości odpowiedzi— jest ona automatycznie kierowana z powrotem do źródła: Nadawca.

Po poprawnym skonfigurowaniu nadawcy i odbiorcy można teraz uruchomić rozwiązanie.

Uruchamianie rozwiązania

Aby uruchomić rozwiązanie, należy uruchomić zarówno nadawcę, jak i odbiorcę. Jeśli używasz Visual Studio Code, uruchom konfigurację "Debuguj wszystko". Jeśli używasz programu Visual Studio, skonfiguruj rozwiązanie tak, aby uruchamiało projekty Nadawca i Odbiorca:

  1. Kliknij prawym przyciskiem myszy rozwiązanie w Eksplorator rozwiązań
  2. Wybierz pozycję "Ustaw projekty startowe..."
  3. Wybieranie wielu projektów startowych
  4. W przypadku nadawcy i odbiorcy wybierz pozycję "Uruchom" na liście rozwijanej

Uruchom rozwiązanie. Zostaną wyświetlone dwie aplikacje konsolowe: jedna dla nadawcy i jedna dla odbiornika.

W nadawcy zwróć uwagę, że Ping komunikat jest wysyłany co sekundę dzięki SenderWorker zadaniu w tle. Odbiorca wyświetla szczegóły każdego Ping odbieranego komunikatu, a nadawca rejestruje szczegóły każdego Pong odbieranego komunikatu w odpowiedzi.

Teraz, gdy wszystko działa, przerwijmy go.

Odporność w działaniu

Błędy to fakt życia w systemach oprogramowania. Jest nieuniknione, że kod ulegnie awarii i może to zrobić z różnych powodów, takich jak awarie sieci, blokady bazy danych, zmiany w interfejsie API innej firmy i zwykłe stare błędy kodowania.

NServiceBus ma niezawodne funkcje odzyskiwania na potrzeby obsługi błędów. Gdy program obsługi komunikatów zakończy się niepowodzeniem, komunikaty są automatycznie ponawiane na podstawie wstępnie zdefiniowanych zasad. Istnieją dwa typy zasad ponawiania prób: natychmiastowe ponawianie prób i opóźnione próby. Najlepszym sposobem opisania sposobu ich działania jest sprawdzenie ich w działaniu. Dodajmy zasady ponawiania prób do punktu końcowego odbiornika:

  1. Otwórz Program.cs w projekcie Nadawca
  2. .EnableInstallers Po wierszu dodaj następujący kod:
endpointConfiguration.SendFailedMessagesTo("error");
var recoverability = endpointConfiguration.Recoverability();
recoverability.Immediate(
    immediate =>
    {
        immediate.NumberOfRetries(3);
    });
recoverability.Delayed(
    delayed =>
    {
        delayed.NumberOfRetries(2);
        delayed.TimeIncrease(TimeSpan.FromSeconds(5));
    });

Zanim omówimy, jak działają te zasady, zobaczmy ją w działaniu. Przed przetestowaniem zasad możliwości odzyskiwania należy zasymulować błąd. PingHandler Otwórz kod w projekcie Odbiornik i usuń komentarz w tym wierszu:

throw new Exception("BOOM");

Teraz, gdy odbiornik obsługuje komunikat, zakończy się niepowodzeniem Ping . Uruchom rozwiązanie ponownie i zobaczmy, co się dzieje w odbiorniku.

Dzięki naszemu mniej niezawodnemu PingHandlerprogramowi wszystkie komunikaty kończą się niepowodzeniem. Zasady ponawiania prób są widoczne dla tych komunikatów. Gdy komunikat po raz pierwszy zakończy się niepowodzeniem, zostanie on natychmiast ponowiony do trzech razy:

Obraz przedstawiający zasady natychmiastowego ponawiania próby, które ponawiają próby komunikaty do 3 razy

Oczywiście nadal nie powiedzie się, więc gdy są używane trzy natychmiastowe ponawianie prób, opóźnione zasady ponawiania są uruchamiane, a komunikat jest opóźniony przez 5 sekund:

Obraz przedstawiający opóźnione zasady ponawiania prób, które opóźniają komunikaty w przyrostach o 5 sekund przed podjęciem kolejnej rundy natychmiastowych ponownych prób

Po upływie tych 5 sekund komunikat zostanie ponowiony ponownie trzy razy (czyli kolejna iteracji natychmiastowych zasad ponawiania). Spowoduje to również niepowodzenie, a usługa NServiceBus ponownie opóźni komunikat, tym razem przez 10 sekund, zanim spróbuje ponownie.

Jeśli PingHandler nadal nie powiedzie się po wykonaniu pełnych zasad ponawiania, komunikat zostanie umieszczony w scentralizowanej kolejce błędów o nazwie , zgodnie errorz definicją wywołania metody SendFailedMessagesTo.

Obraz przedstawiający komunikat o niepowiódłym

Koncepcja scentralizowanej kolejki błędów różni się od mechanizmu martwych listów w Azure Service Bus, który ma kolejkę utraconych komunikatów dla każdej kolejki przetwarzania. W przypadku NServiceBus kolejki utraconych wiadomości w Azure Service Bus działają jako prawdziwe kolejki komunikatów otruciach, podczas gdy komunikaty, które kończą się w scentralizowanej kolejce błędów, można ponownie przetworzyć w późniejszym czasie, w razie potrzeby.

Zasady ponawiania prób pomagają rozwiązać kilka typów błędów , które często są przejściowe lub częściowo przejściowe w naturze. Oznacza to, że błędy, które są tymczasowe i często odejdą, jeśli komunikat jest po prostu ponownie przetworzony po krótkim opóźnieniu. Przykłady obejmują awarie sieci, blokady bazy danych i awarie interfejsu API innych firm.

Gdy komunikat znajduje się w kolejce błędów, możesz sprawdzić szczegóły komunikatu w wybranym narzędziu, a następnie zdecydować, co z nim zrobić. Na przykład przy użyciu usługi ServicePulse narzędzie do monitorowania według określonego oprogramowania możemy wyświetlić szczegóły komunikatu i przyczynę niepowodzenia:

Obraz przedstawiający usługę ServicePulse z określonego oprogramowania

Po sprawdzeniu szczegółów możesz wysłać wiadomość z powrotem do oryginalnej kolejki na potrzeby przetwarzania. Przed wykonaniem tej czynności możesz również edytować komunikat. Jeśli w kolejce błędów znajduje się wiele komunikatów, które zakończyły się niepowodzeniem z tego samego powodu, wszystkie mogą być wysyłane z powrotem do oryginalnych miejsc docelowych jako partia.

Następnie nadszedł czas, aby dowiedzieć się, gdzie wdrożyć nasze rozwiązanie na platformie Azure.

Gdzie hostować usługi na platformie Azure

W tym przykładzie punkty końcowe nadawcy i odbiorcy są skonfigurowane do uruchamiania jako aplikacji konsolowych. Mogą być one również hostowane w różnych usługach platformy Azure, takich jak Azure Functions, usługi aplikacja systemu Azure, Azure Container Instances, usługi Azure Kubernetes Services i maszyny wirtualne platformy Azure. Na przykład poniżej przedstawiono sposób konfigurowania punktu końcowego nadawcy do uruchamiania jako funkcji platformy Azure:

[assembly: FunctionsStartup(typeof(Startup))]
[assembly: NServiceBusEndpointName("Sender")]

public class Startup : FunctionsStartup
{
    public override void Configure(IFunctionsHostBuilder builder)
    {
        builder.UseNServiceBus(() =>
        {
            var configuration = new ServiceBusTriggeredEndpointConfiguration("Sender");
            var transport = configuration.AdvancedConfiguration.Transport;
            transport.Routing().RouteToEndpoint(typeof(Ping), "Receiver");

            return configuration;
        });
    }
}

Aby uzyskać więcej informacji na temat korzystania z NServiceBus z usługą Functions, zobacz Azure Functions z Azure Service Bus w dokumentacji NServiceBus.

Następne kroki

Aby uzyskać więcej informacji na temat korzystania z sieci NServiceBus z usługami platformy Azure, zobacz następujące artykuły: