How do you handle inter-service communication in microservices?

4 minadvancedmicroservicescommunicationmessaging

Quick Answer

Services communicate synchronously (request/response via HTTP/REST or gRPC) or asynchronously (messaging/events via a broker like RabbitMQ, Kafka, or Azure Service Bus). Synchronous is simple but couples availability and adds latency; asynchronous messaging decouples services, improves resilience, and suits event-driven workflows but adds eventual consistency and infrastructure. Choose per interaction, often combining both.

Detailed Answer

Inter-service communication can be synchronous or asynchronous.

Synchronous Communication (Request/Response):

1. HTTP/REST with HttpClient:

// Startup.cs
services.AddHttpClient("OrderService", c =>
{
    c.BaseAddress = new Uri("http://order-service:5000");
    c.DefaultRequestHeaders.Add("Accept", "application/json");
});

// Service implementation
public class ProductService
{
    private readonly IHttpClientFactory _httpClientFactory;
    
    public ProductService(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }
    
    public async Task CheckOrderStatus(int orderId)
    {
        var client = _httpClientFactory.CreateClient("OrderService");
        var response = await client.GetAsync($"/api/orders/{orderId}/status");
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync();
    }
}

2. gRPC (High-performance RPC):

// order.proto
syntax = "proto3";

service OrderService {
  rpc GetOrder (OrderRequest) returns (OrderResponse);
  rpc CreateOrder (CreateOrderRequest) returns (OrderResponse);
}

message OrderRequest {
  int32 order_id = 1;
}

message OrderResponse {
  int32 order_id = 1;
  string status = 2;
  double total = 3;
}
// gRPC Server
public class OrderServiceImpl : OrderService.OrderServiceBase
{
    public override async Task GetOrder(OrderRequest request, ServerCallContext context)
    {
        var order = await _orderRepository.GetByIdAsync(request.OrderId);
        return new OrderResponse
        {
            OrderId = order.Id,
            Status = order.Status,
            Total = order.Total
        };
    }
}

// gRPC Client
public class ProductService
{
    private readonly OrderService.OrderServiceClient _orderClient;
    
    public async Task GetOrderDetails(int orderId)
    {
        var request = new OrderRequest { OrderId = orderId };
        return await _orderClient.GetOrderAsync(request);
    }
}

Asynchronous Communication (Message-based):

1. RabbitMQ:

// Install: Install-Package RabbitMQ.Client

// Message Publisher
public class OrderCreatedPublisher
{
    private readonly IConnection _connection;
    
    public void PublishOrderCreated(OrderCreatedEvent orderEvent)
    {
        using var channel = _connection.CreateModel();
        
        channel.ExchangeDeclare("orders", ExchangeType.Topic, durable: true);
        
        var message = JsonSerializer.Serialize(orderEvent);
        var body = Encoding.UTF8.GetBytes(message);
        
        channel.BasicPublish(
            exchange: "orders",
            routingKey: "order.created",
            basicProperties: null,
            body: body);
    }
}

// Message Consumer
public class InventoryService : BackgroundService
{
    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var channel = _connection.CreateModel();
        channel.QueueDeclare("inventory-queue", durable: true, exclusive: false);
        channel.QueueBind("inventory-queue", "orders", "order.created");
        
        var consumer = new EventingBasicConsumer(channel);
        consumer.Received += (model, ea) =>
        {
            var body = ea.Body.ToArray();
            var message = Encoding.UTF8.GetString(body);
            var orderEvent = JsonSerializer.Deserialize(message);
            
            // Process the order
            ProcessOrder(orderEvent);
        };
        
        channel.BasicConsume("inventory-queue", autoAck: true, consumer);
        return Task.CompletedTask;
    }
}

2. Azure Service Bus:

// Install: Install-Package Azure.Messaging.ServiceBus

public class ServiceBusPublisher
{
    private readonly ServiceBusClient _client;
    private readonly ServiceBusSender _sender;
    
    public async Task PublishAsync(T message)
    {
        var messageBody = JsonSerializer.Serialize(message);
        var serviceBusMessage = new ServiceBusMessage(messageBody);
        await _sender.SendMessageAsync(serviceBusMessage);
    }
}

public class ServiceBusConsumer : BackgroundService
{
    private readonly ServiceBusProcessor _processor;
    
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _processor.ProcessMessageAsync += MessageHandler;
        _processor.ProcessErrorAsync += ErrorHandler;
        
        await _processor.StartProcessingAsync(stoppingToken);
    }
    
    private async Task MessageHandler(ProcessMessageEventArgs args)
    {
        var body = args.Message.Body.ToString();
        var order = JsonSerializer.Deserialize(body);
        
        // Process message
        await ProcessOrderAsync(order);
        await args.CompleteMessageAsync(args.Message);
    }
}