Microservices are designed to be independent, but they still need to interact with each other to fulfill broader business workflows. In .NET, communication between microservices can be synchronous or asynchronous. Choosing the right approach depends on your performance requirements, architecture complexity, and reliability goals.

Synchronous Communication

Synchronous communication means one service directly calls another and waits for a response. This is typically used for immediate, request-response scenarios.

HTTP Communication (REST via HttpClient)

HTTP is the most widely used communication method, particularly suitable for external APIs or simple internal interactions.

Example: Product Service exposing an endpoint

[ApiController]
[Route("api/products")]
public class ProductController : ControllerBase
{ [HttpGet("{id}")] public IActionResult GetProduct(int id) { var product = new Product { Id = id, Name = "Laptop", Price = 999.99 }; return Ok(product); }
}

Register HttpClient in Program.cs

builder.Services.AddHttpClient<IProductClient, ProductClient>(client =>{ client.BaseAddress = new Uri("https://localhost:7011/");
});

Client Interface and Implementation

public interface IProductClient
{ Task<Product> GetProductAsync(int id);
}
public class ProductClient : IProductClient
{ private readonly HttpClient _httpClient; public ProductClient(HttpClient httpClient) { _httpClient = httpClient; } public async Task<Product> GetProductAsync(int id) { return await _httpClient.GetFromJsonAsync<Product>($"api/products/{id}") ?? throw new Exception("Product not found"); }
}

Order Service Consuming the Product Service

[ApiController]
[Route("api/orders")]
public class OrderController : ControllerBase
{ private readonly IProductClient _productClient; public OrderController(IProductClient productClient) { _productClient = productClient; } [HttpPost] public async Task<IActionResult> CreateOrder(int productId) { var product = await _productClient.GetProductAsync(productId); // Logic to create order return Ok($"Order placed for {product.Name}"); }
}

Advantages:

  • Simple and widely supported
  • Easy to debug and monitor

Drawbacks:

  • Can cause tight coupling
  • Latency and error handling need to be managed explicitly

gRPC (for Efficient Binary Communication)

gRPC is a high-performance framework that uses HTTP/2 and Protocol Buffers. It’s a good fit for internal communication where speed and efficiency are important.gRPC Service Definition (product.proto)

syntax = "proto3";
service Product { rpc GetProduct (ProductRequest) returns (ProductResponse);
}
message ProductRequest { int32 id = 1;
}
message ProductResponse { int32 id = 1; string name = 2; double price = 3;
}

Server-Side Registration

app.MapGrpcService<ProductServiceImpl>();

Client-Side Call

var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Product.ProductClient(channel);
var reply = await client.GetProductAsync(new ProductRequest { Id = 1 });
Console.WriteLine(reply.Name);

Advantages:

  • High throughput, low latency
  • Contract-first development with strong typing

Drawbacks:

  • More setup required
  • Not as straightforward for public APIs

Conclusion

In .NET microservices, you have two main options for synchronous communication:

  • Use HttpClient (via IHttpClientFactory) for simple REST-based calls.
  • Use gRPC for high-performance internal communication between services.

In many real-world systems, both methods are used together, REST for public-facing endpoints, and gRPC internally where performance is critical. If you’re interested, we can also cover asynchronous messaging patterns using message brokers like RabbitMQ or Azure Service Bus.