This episode builds integrated systems: background and hosted services, message brokers such as RabbitMQ, Kafka, and Azure Service Bus, gRPC and REST integration between services, and distributed transactions and eventual consistency in distributed architectures.

Modern applications rarely stand alone. Episode 17 covers messaging and integration — how .NET services communicate, process background work, and stay consistent when split into many services.
You will learn hosted services for background work, integration with message brokers such as RabbitMQ, Kafka, and Azure Service Bus, gRPC and REST communication, and how to handle consistency in distributed systems — where a single database transaction is no longer enough.
A hosted service is a service that runs for the lifetime of the application inside the Generic Host. The worker template provides the skeleton:
dotnet new worker -n OrderWorkerdotnet new worker -n OrderWorker creates a project with a Worker that implements BackgroundService — the ExecuteAsync method runs as soon as the host starts, and is stopped cleanly when the host shuts down.
A worker class processes a queue in a loop:
public class OrderWorker : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
Console.WriteLine("Cek pesanan baru");
}
}
}ExecuteAsync keeps running until stoppingToken is cancelled when the host stops. This pattern is used for scheduled jobs, cache cleanup, and background integrations.
A message broker mediates communication between services through queues or topics. Producers send messages without waiting for consumers; consumers process at their own pace. This decouples services in time.
The MassTransit library unifies broker integration with a clean abstraction:
dotnet add package MassTransit
dotnet add package MassTransit.RabbitMQConnection configuration:
builder.Services.AddMassTransit(x =>
{
x.UsingRabbitMq((context, cfg) =>
{
cfg.Host("localhost", "/", h =>
{
h.Username("guest");
h.Password("guest");
});
});
});UsingRabbitMq connects the bus to the broker. Once registered, services can send messages via IPublishEndpoint or receive them through consumers. Switching brokers (for example to Azure Service Bus) only requires changing one line of configuration — the business code stays the same.
A guide:
gRPC provides high performance with a binary protocol for inter-service communication. Contract definitions are written in a .proto file:
syntax = "proto3";
service Katalog {
rpc GetProduk(ProdukId) returns (Produk);
}
message ProdukId { int32 id = 1; }
message Produk { string nama = 1; double harga = 2; }gRPC compiles the .proto file into strongly typed C# clients and servers. For latency-sensitive internal communication between services, gRPC outperforms REST thanks to binary serialization and HTTP/2.
Use REST at open system boundaries for external clients — browsers, mobile, and partners — because of its readability and universal tooling support. The common pattern: REST at the edge, gRPC inside. Both coexist in ASP.NET Core without conflict.
In microservices, an operation spans many databases — a single ACID transaction does not apply. The pragmatic solution is eventual consistency: the system reaches consistency after a period of time, driven by events.
public class OutboxMessage
{
public Guid Id { get; set; }
public string Tipe { get; set; }
public string Payload { get; set; }
public bool Dikirim { get; set; }
}The transactional outbox pattern stores events in the same table as the business transaction, then a worker sends them to the broker. This guarantees events are not lost: if delivery fails, the message stays in the outbox and is retried.
For long flows across services, use a saga: every step has a compensating counterpart. If a step fails, the saga runs the compensation of the previous steps — for example, cancel the order if payment fails. Consistency is achieved eventually, not atomically.
Tip
Design messages to be idempotent: consumers must be able to process the same message more than once without double effects. This drastically simplifies retries and compensation.
Key takeaways:
In the next episode 18 we will discuss modern tooling and build automation — the dotnet CLI, MSBuild, and SDK-style projects, continuous integration with GitHub Actions, source generators and Roslyn analyzers, and code formatting, linting, and reproducible builds.