Episode ini membahas arsitektur microservices di .NET: prinsip cloud-native dan container, minimal APIs dan gRPC, resilience patterns dengan Polly, messaging dengan MassTransit, serta observability pattern yang wajib dimiliki tiap microservice.

Aplikasi raksasa yang monolitik akhirnya sulit dikelola: satu fitur bermasalah bisa menahan rilis seluruh aplikasi. Arsitektur microservices memecah aplikasi menjadi layanan kecil yang mandiri — masing-masing bisa dikembangkan, di-deploy, dan diskalakan sendiri.
Tapi microservices bukan tanpa biaya. Jaringan antar layanan bisa gagal, menemukan layanan menjadi masalah, dan memantau puluhan proses lebih sulit. .NET menyediakan ekosistem lengkap untuk menangani semua itu.
Episode 17 ini membahas prinsip cloud-native, minimal APIs dan gRPC, resilience patterns, messaging dengan MassTransit, dan observability yang wajib ada di tiap layanan.
Setiap microservice sebaiknya memiliki:
Dengan prinsip ini, tim bisa bergerak cepat dan kegagalan terisolasi pada satu layanan saja.
Untuk HTTP API sederhana, minimal APIs jauh lebih ringkas daripada controller:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/api/produk/{id:int}", (int id) =>
Results.Ok(new { Id = id, Nama = "Kopi Gayo" }));
app.MapPost("/api/produk", (Produk produk) =>
Results.Created($"/api/produk/{produk.Id}", produk));
app.Run();Handler lambda di MapGet dan MapPost menerima parameter yang di-binding otomatis dari route dan body. Untuk layanan internal yang butuh performa tinggi, pilih gRPC yang memakai HTTP/2 dan serialisasi binary:
public class ProdukService : ProdukServiceBase
{
public override Task<ProdukReply> GetProduk(
ProdukRequest request, ServerCallContext context)
{
return Task.FromResult(new ProdukReply
{
Id = request.Id,
Nama = "Kopi Gayo"
});
}
}Aturan praktis: gRPC untuk komunikasi antar layanan internal (disebut service-to-service), REST/minimal API untuk konsumsi eksternal seperti browser dan mobile.
Di dunia jaringan, kegagalan itu normal, bukan pengecualian. Polly adalah library resilience untuk .NET — retry dengan backoff, circuit breaker, dan timeout:
dotnet add src/OrderService package Microsoft.Extensions.Http.Resiliencebuilder.Services.AddHttpClient<OrderClient>(c =>
c.BaseAddress = new Uri("http://payment-service:8080"))
.AddStandardResilienceHandler(options =>
{
options.Retry.MaxRetryAttempts = 3;
options.Retry.BackoffType = DelayBackoffType.Exponential;
options.CircuitBreaker.MinimumThroughput = 10;
});AddStandardResilienceHandler mengombinasikan retry, circuit breaker, dan timeout dalam satu pipeline. Pola service discovery memastikan OrderClient selalu menemukan alamat payment-service yang benar — di Kubernetes, nama layanan sudah menjadi alamat DNS yang otomatis di-resolve.
Saat dua layanan harus bekerja sama tanpa saling menunggu, gunakan messaging. MassTransit adalah bus messaging populer yang mendukung RabbitMQ dan Azure Service Bus:
public class PesananDibuat
{
public Guid OrderId { get; set; }
public decimal Total { get; set; }
}
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMassTransit(x =>
{
x.UsingRabbitMq((ctx, cfg) =>
cfg.Host("rabbitmq://localhost"));
});
var bus = app.Services.GetRequiredService<IPublishEndpoint>();
await bus.Publish(new PesananDibuat
{
OrderId = Guid.NewGuid(),
Total = 250_000m
});IPublishEndpoint.Publish mengirim event ke bus tanpa menunggu konsumen. Layanan lain bisa subscribe event yang sama, sehingga layanan terhubung lewat event, bukan panggilan langsung.
Sebelum memakai bus, pastikan paket transport yang sesuai sudah terpasang. Untuk RabbitMQ, dotnet add package MassTransit.RabbitMQ menambahkan transport yang dipakai kode di atas; untuk Azure Service Bus, ganti dengan MassTransit.Azure.ServiceBus.Core. Konfigurasi cfg.Host("rabbitmq://localhost") sudah cukup untuk development lokal.
Setiap microservice wajib menerbitkan tiga sinyal: log, metrics, dan traces. Tanpa ini, tracing sebuah request yang melewati sepuluh layanan hampir mustahil. Konsep correlation ID menghubungkan satu request di semua layanan:
app.Use(async (context, next) =>
{
var traceId = Activity.Current?.TraceId.ToString()
?? Guid.NewGuid().ToString();
context.Response.Headers["X-Trace-Id"] = traceId;
await next();
});Header X-Trace-Id yang sama diturunkan ke panggilan keluar, sehingga log dari berbagai layanan bisa digabungkan berdasarkan ID yang sama. Tooling observability lengkap akan kita bangun di episode 20.
Inti yang harus dibawa pulang:
Di episode 18 selanjutnya kita merapikan proses pengembangan: tooling modern dan build automation — dotnet CLI dan MSBuild, SDK-style projects, build automation dengan GitHub Actions, source generators dan analyzer Roslyn, serta code formatting dan reproducible builds.