In this lesson, you will learn how to abstract service discovery behind your Dependency Injection (DI) container so your microservices can resolve endpoints dynamically without hardcoded addresses.
The Problem: Hardcoded URLs vs. Dynamic Environments
In a microservice architecture, your services are constantly moving. If you hardcode a service's base URL into a configuration file, your application becomes brittle. If that service scales or moves to a new cluster node, your client breaks. Service discovery allows you to look up a service’s current network location (via tools like Consul, Kubernetes DNS, or Eureka) at runtime.
Integrating Discovery into the DI Registry
Instead of injecting a fixed HttpClient with a base address, we inject a "resolver" or a factory that asks the service discovery system for the current location. We can register this abstraction within our DI container during startup, keeping our business logic clean.
Consider this pattern where we register an IHttpClientFactory that uses a custom DelegatingHandler to resolve the address:
// Registering a discovery-aware client
services.AddHttpClient("OrderService", client =>
{
// The base address is dynamic, so we leave it blank here
})
.AddHttpMessageHandler<ServiceDiscoveryHandler>();
// The handler interceptor resolves the address just before the request
public class ServiceDiscoveryHandler : DelegatingHandler
{
private readonly IDiscoveryService _discovery;
public ServiceDiscoveryHandler(IDiscoveryService discovery) => _discovery = discovery;
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken ct)
{
var serviceName = request.RequestUri.Host;
var endpoint = await _discovery.ResolveAsync(serviceName);
request.RequestUri = new Uri(endpoint + request.RequestUri.PathAndQuery);
return await base.SendAsync(request, ct);
}
}
By wrapping the resolution logic in a DelegatingHandler and injecting it via DI, your core service classes remain completely unaware of how or where the service is discovered. They simply call the "OrderService" host, and the DI container ensures the infrastructure handles the lookup transparently. This keeps your service registration clean and ensures that every time a request is made, your client is using the most up-to-date location provided by your registry.
Key Takeaway: Abstract service discovery behind a DI-managed interface or HTTP handler so your services resolve endpoints dynamically, keeping your code decoupled from environment-specific infrastructure.