Membangun aplikasi bisnis berbasis pesan dengan NServiceBus dan Azure Service Bus
NServiceBus adalah kerangka kerja olahpesan komersial yang disediakan oleh Particular Software. NServiceBus dibangun di atas Azure Service Bus dan membantu pengembang berfokus pada logika bisnis dengan mengabstraksikan masalah infrastruktur. Dalam panduan ini, kami akan membangun solusi yang bertukar pesan antara dua layanan. Kami juga akan menunjukkan cara mencoba ulang pesan yang gagal secara otomatis dan meninjau opsi untuk hosting layanan ini di Azure.
Catatan
Kode untuk tutorial ini tersedia pada situs web Dokumentasi Particular Software.
Prasyarat
Sampel berasumsi bahwa Anda telah membuat namespace layanan Azure Service Bus.
Penting
NServiceBus membutuhkan setidaknya tingkat Standar. Tingkat dasar tidak akan berfungsi.
Mengunduh dan menyiapkan solusi
Unduh kode dari Situs web Dokumentasi Particular Software. Solusi
SendReceiveWithNservicebus.sln
berisi tiga proyek:- Pengirim: aplikasi konsol yang mengirim pesan
- Penerima: aplikasi konsol yang menerima pesan dari pengirim dan membalas pesan tersebut
- Dibagikan: pustaka kelas berisi kontrak pesan yang dibagikan antara pengirim dan penerima
Diagram berikut yang dihasilkan oleh ServiceInsight, suatu visualisasi dan alat penelusuran kesalahan dari Perangkat Lunak Tertentu, menunjukkan alur pesan:
Buka
SendReceiveWithNservicebus.sln
di editor kode favorit Anda (Misalnya, Visual Studio 2022).Buka
appsettings.json
dalam proyek Penerima dan Pengirim kemudian aturAzureServiceBusConnectionString
ke string koneksi untuk namespace layanan Azure Service Bus Anda.- Ini dapat ditemukan di portal Azure di bawah Bus Layanan Pengaturan Namespace>Layanan>Kebijakan akses bersama RootManageSharedAccessKey>>String Koneksi Utama .
- juga
AzureServiceBusTransport
memiliki konstruktor yang menerima namespace layanan dan kredensial token, yang dalam lingkungan produksi akan lebih aman, namun untuk tujuan tutorial ini kunci akses bersama string koneksi akan digunakan.
Menentukan kontrak pesan bersama
Pustaka kelas Bersama adalah tempat Anda menentukan kontrak yang digunakan untuk mengirim pesan kita. Tempat ini mencakup referensi ke paket NuGet NServiceBus
yang berisi antarmuka yang dapat Anda gunakan untuk mengidentifikasi pesan kita. Antarmuka tidak diperlukan, tetapi ia memberi kita beberapa validasi tambahan dari NServiceBus dan memungkinkan kode untuk melakukan dokumentasi secara mandiri.
Pertama, kita akan mengulas kelas Ping.cs
public class Ping : NServiceBus.ICommand
{
public int Round { get; set; }
}
Kelas Ping
menentukan pesan yang dikirim oleh Pengirim kepada Penerima. Kelas ini merupakan kelas C # sederhana yang menerapkan NServiceBus.ICommand
, antarmuka paket NServiceBus. Pesan ini merupakan sinyal kepada pembaca dan NServiceBus bahwa pesan ini adalah perintah, meskipun ada cara lain untuk mengidentifikasi pesan tanpa menggunakan antarmuka.
Kelas pesan lainnya dalam proyek Bersama adalah Pong.cs
:
public class Pong : NServiceBus.IMessage
{
public string Acknowledgement { get; set; }
}
Pong
juga merupakan objek C# meskipun ia mengimplementasikan NServiceBus.IMessage
. Antarmuka IMessage
mewakili pesan generik yang bukan merupakan perintah maupun kejadian, dan secara umum digunakan untuk balasan. Dalam sampel kita, ini adalah balasan yang dikirim kembali oleh Penerima ke Pengirim untuk menunjukkan bahwa pesan telah diterima.
Ping
dan Pong
adalah dua jenis pesan yang akan Anda gunakan. Langkah selanjutnya adalah mengonfigurasi Pengirim untuk menggunakan Azure Service Bus dan mengirim pesan Ping
.
Menyiapkan pengirim
Pengirim adalah titik akhir yang mengirim pesan Ping
kita. Di sini, Anda mengonfigurasi Pengirim untuk menggunakan Azure Service Bus sebagai mekanisme transportasi, kemudian membangun instans Ping
dan mengirimnya.
Dalam metode Main
Program.cs
, Anda mengonfigurasi titik akhir Pengirim:
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();
Banyak hal harus dibahas di sini, kami akan meninjaunya satu per satu.
Mengonfigurasi host untuk titik akhir
Hosting dan pengelogan dikonfigurasi menggunakan opsi Microsoft Generic Host standar. Untuk saat ini, titik akhir dikonfigurasi untuk berjalan sebagai aplikasi konsol tetapi dapat diubah untuk berjalan di Azure Functions dengan perubahan minimal, yang akan kita bahas nanti di artikel ini.
Mengonfigurasi titik akhir NServiceBus
Selanjutnya, Anda meminta host untuk menggunakan NServiceBus dengan metode ekstensi .UseNServiceBus(…)
. Metode ini mengambil fungsi panggilan balik yang mengembalikan titik akhir yang akan dimulai saat host berjalan.
Dalam konfigurasi titik akhir, Anda menentukan AzureServiceBus
untuk transportasi kita, yang menyediakan string koneksi dari appsettings.json
. Selanjutnya, Anda akan mengatur perutean sehingga pesan jenis Ping
dikirim ke titik akhir bernama "Receiver". Pengaturan ini memungkinkan NServiceBus mengotomatiskan proses pengiriman pesan ke tujuan tanpa memerlukan alamat penerima.
Panggilan untuk EnableInstallers
akan menyiapkan topologi kita di namespace layanan Azure Service Bus ketika titik akhir diluncurkan, yang menciptakan antrean yang diperlukan jika diperlukan. Dalam lingkungan produksi, pembuatan skrip operasional adalah opsi lain untuk membuat topologi.
Menyiapkan layanan latar belakang untuk mengirim pesan
Bagian terakhir dari pengirim adalah SenderWorker
, yakni layanan latar belakang yang dikonfigurasi untuk mengirim pesan Ping
setiap detik.
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
}
}
}
IMessageSession
yang digunakan dalam ExecuteAsync
diinjeksi ke SenderWorker
dan memungkinkan kita mengirim pesan menggunakan NServiceBus di luar penangan pesan. Perutean yang Anda konfigurasi di Sender
menentukan tujuan pesan Ping
. Penentuan ini menjaga topologi sistem (pesan dirutekan ke alamat tertentu) sebagai masalah yang terpisah dari kode bisnis.
Aplikasi Pengirim juga berisi PongHandler
. Anda akan kembali setelah kita berdiskusi mengenai Penerima, yang akan kita lakukan selanjutnya.
Penyiapan penerima
Penerima adalah titik akhir yang mendengarkan pesan Ping
, akan mencatat ketika pesan diterima, dan membalas pesan kepada pengirim. Di bagian ini, kita akan dengan cepat meninjau konfigurasi titik akhir, yang serupa dengan Pengirim, kemudian mengalihkan perhatian kita ke penangan pesan.
Seperti pengirim, siapkan penerima sebagai aplikasi konsol menggunakan Microsoft Generic Host. Penyiapan ini menggunakan konfigurasi pengelogan dan titik akhir yang sama (dengan Azure Service Bus sebagai transportasi pesan) tetapi dengan nama yang berbeda, untuk membedakannya dari pengirim:
var endpointConfiguration = new EndpointConfiguration("Receiver");
Karena titik akhir ini hanya membalas pembuatnya dan tidak memulai percakapan baru, maka konfigurasi perutean tidak diperlukan. Konfigurasi ini juga tidak memerlukan pekerja latar belakang seperti yang diperlukan Pengirim, karena penerima hanya membalas ketika menerima pesan.
Penangan pesan Ping
Proyek Penerima berisi penangan pesan bernama 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);
}
}
Untuk saat ini, mari abaikan kode yang dikomentari; kita akan kembali saat membicarakan pulih dari kegagalan.
Kelas menerapkan IHandleMessages<Ping>
yang menentukan metode: Handle
. Antarmuka ini memberi tahu NServiceBus bahwa ketika titik akhir menerima pesan jenis Ping
, maka pesan tersebut harus diproses dengan metode Handle
dalam penangan ini. Metode Handle
menjadikan pesan tersebut parameter, dan IMessageHandlerContext
, yang memungkinkan operasi olahpesan lebih lanjut, seperti membalas, mengirim perintah, atau menerbitkan kejadian.
Kami PingHandler
mudah: ketika pesan Ping
diterima, catat detail pesan dan balas kembali ke pengirim dengan pesan baru Pong
, yang kemudian ditangani di Pengirim PongHandler
.
Catatan
Dalam konfigurasi Pengirim, Anda menentukan bahwa pesan Ping
harus dirutekan ke Penerima. NServiceBus menambahkan metadata ke pesan yang menunjukkan, antara lain, asal pesan. Inilah sebabnya Anda tidak perlu menentukan data perutean apa pun untuk pesan balasan Pong
; pesan akan secara otomatis diarahkan kembali ke asalnya: Pengirim.
Setelah Pengirim dan Penerima dikonfigurasi dengan benar, Anda dapat menjalankan solusinya.
Menjalankan solusi
Untuk meluncurkan solusi, Anda perlu menjalankan Pengirim dan Penerima. Jika Anda menggunakan Visual Studio Code, luncurkan konfigurasi "Debug Semua". Jika Anda menggunakan Visual Studio, konfigurasikan solusi untuk meluncurkan proyek Pengirim dan Penerima:
- Klik kanan solusi di Penjelajah Solusi
- Pilih "Atur Proyek Startup..."
- Pilih Beberapa proyek startup
- Untuk Pengirim dan Penerima, pilih "Mulai" dalam daftar dropdown
Luncurkan solusi. Dua aplikasi konsol akan muncul, satu untuk Pengirim dan satu untuk Penerima.
Dalam Pengirim, perhatikan bahwa pesan Ping
dikirim setiap detik berkat pekerjaan latar belakang SenderWorker
. Penerima menampilkan detail pesan Ping
yang ia terima dan Pengirim mencatat detail setiap pesan Pong
yang ia terima sebagai balasan.
Kini semuanya telah berjalan baik, mari kita hancurkan.
Ketahanan dalam tindakan
Kesalahan merupakan hal yang biasa dalam sistem perangkat lunak. Kegagalan kode merupakan hal yang tidak dapat dihindari dan bisa disebabkan oleh berbagai alasan, seperti kegagalan jaringan, kunci database, perubahan dalam API pihak ke-tiga, dan kesalahan pengodean lama biasa.
NServiceBus memiliki fitur pemulihan yang kuat untuk menangani kegagalan. Saat handler pesan gagal, pesan secara otomatis dicoba ulang berdasarkan kebijakan yang telah ditentukan sebelumnya. Ada dua jenis kebijakan percobaan kembali: pengulangan kembali segera dan pengulangan kembali yang tertunda. Cara terbaik untuk menggambarkan cara kerja kebijakan percobaan kembali adalah dengan melihat tindakannya. Mari tambahkan kebijakan percobaan kembali ke titik akhir Penerima kami:
- Buka
Program.cs
dalam proyek Pengirim - Setelah baris
.EnableInstallers
, tambahkan kode berikut:
endpointConfiguration.SendFailedMessagesTo("error");
var recoverability = endpointConfiguration.Recoverability();
recoverability.Immediate(
immediate =>
{
immediate.NumberOfRetries(3);
});
recoverability.Delayed(
delayed =>
{
delayed.NumberOfRetries(2);
delayed.TimeIncrease(TimeSpan.FromSeconds(5));
});
Sebelum kita membahas cara kerja kebijakan, mari kita lihat dalam tindakan. Sebelum Anda menguji kebijakan pemulihan, Anda perlu menyimulasikan kesalahan. Buka kode PingHandler
dalam proyek Penerima dan batalkan komentar baris ini:
throw new Exception("BOOM");
Sekarang, ketika Penerima menangani pesan Ping
, operasi ini akan gagal. Luncurkan solusi kembali dan mari kita lihat apa yang terjadi di Penerima.
Dengan PingHandler
yang kurang dapat diandalkan, semua pesan kita akan gagal. Anda dapat melihat kebijakan percobaan kembali yang mengaktifkan pesan-pesan tersebut. Saat pesan gagal untuk pertama kali, maka pesan tersebut akan dicoba kembali hingga tiga kali:
Tentu saja, itu akan terus gagal sehingga ketika tiga percobaan kembali telah mencapai batas, kebijakan percobaan kembali yang tertunda dimulai dan pesan tertunda selama 5 detik:
Setelah 5 detik berlalu, pesan tersebut dicoba kembali sebanyak tiga kali lagi (yaitu, perulangan lain dari kebijakan percobaan kembali segera). Percobaan ini juga akan gagal dan NServiceBus akan menunda pesan lagi, kali ini selama 10 detik, sebelum mencoba lagi.
Jika PingHandler
masih tidak berhasil setelah menjalankan kebijakan percobaan kembali secara penuh, pesan ditempatkan dalam antrean kesalahan terpusat, yang bernama error
, seperti yang ditentukan oleh panggilan ke SendFailedMessagesTo
.
Konsep antrean kesalahan terpusat berbeda dari mekanisme surat gagal di Azure Service Bus, yang memiliki antrean surat gagal untuk setiap antrean pemrosesan. Dengan NServiceBus, antrean surat gagal di Azure Service Bus bertindak sebagai antrean pesan berbahaya sejati, sedangkan pesan yang berakhir di antrean kesalahan terpusat dapat diproses kembali di lain waktu, apabila perlu.
Kebijakan percobaan kembali membantu menyelesaikan beberapa jenis kesalahan yang sering kali bersifat sementara atau semi-sementara. Artinya, kesalahan yang bersifat sementara dan sering teratasi jika pesan hanya diproses kembali setelah penundaan singkat. Contohnya termasuk kegagalan jaringan, kunci database, dan ketidaktersediaan API pihak ke-tiga.
Setelah pesan berada dalam antrean kesalahan, Anda dapat memeriksa detail pesan dalam alat pilihan Anda, lalu memutuskan apa yang harus dilakukan dengan itu. Misalnya, menggunakan ServicePulse, yaitu alat pemantauan oleh Particular Software, kita dapat melihat detail pesan dan alasan dibalik kegagalan tersebut:
Setelah memeriksa detail, Anda dapat mengirim pesan kembali ke antrean aslinya untuk diproses. Anda juga dapat mengedit pesan sebelum melakukannya. Jika terdapat beberapa pesan dalam antrean kesalahan, yang gagal karena alasan yang sama, semuanya dapat dikirim kembali ke tujuan semula sebagai batch.
Selanjutnya, saatnya mencari tahu tempat untuk menyebarkan solusi kita di Azure.
Tempat untuk menjadi host layanan di Azure
Dalam sampel ini, titik akhir Pengirim dan Penerima dikonfigurasi untuk dijalankan sebagai aplikasi konsol. Titik akhir tersebut juga dapat dihosting di berbagai layanan Azure termasuk Azure Functions, Azure App Services, Azure Container Instances, Azure Kubernetes Services, dan Azure VM. Misalnya, berikut adalah cara titik akhir Pengirim dapat dikonfigurasi untuk dijalankan sebagai Fungsi Azure:
[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();
}
}
Untuk informasi lebih lanjut penggunaan NServiceBus dengan Functions, lihat Azure Functions dengan Azure Service Bus dalam dokumentasi NServiceBus.
Langkah berikutnya
Untuk informasi lebih lanjut penggunaan dengan NServiceBus dengan layanan Azure, lihat artikel berikut: