Introduction

Microservices architecture has become one of the most popular approaches in modern software development because it addresses many limitations of monolithic systems, such as scalability constraints, slow deployments, and tightly coupled codebases.

However, microservices also introduce new challenges. One of the most critical questions is:

How do services find and communicate with each other when their IP addresses and ports change frequently?

This is where Service Discovery becomes essential.

In this article, you will learn:

What Is Service Discovery?

Service discovery is the process in distributed systems for dynamically locating services and enabling communication between them.

In a microservices environment:

Because of this, a service’s network location (IP address and port) can change at any time.

Hardcoding service URLs such as:

https://localhost:5002/api/payment

makes the system fragile.

Instead, services communicate using logical names, for example:

payment-service

Service discovery resolves these logical names to actual network locations at runtime.

Why Service Discovery Is Needed in Microservices

In a monolithic application, modules communicate through direct method calls. In microservices, communication happens over the network.

Without service discovery, teams would need to manually manage:

This approach does not scale and quickly becomes unmanageable in production environments.

How Service Discovery Works

Service discovery typically operates in two main steps.

1. Service Registration

When a microservice starts, it registers itself with a service registry by providing:

The registry maintains an up-to-date list of all available service instances.

2. Service Lookup (Discovery)

When a service needs to call another service, it queries the registry:

Where is payment-service running?

The registry responds with the network addresses of one or more healthy instances. The calling service then connects to one of those instances.

Benefits of Service Discovery in Microservices

1. Dynamic Scalability

Microservices can scale automatically.

Example:

2. Resilience

Failures are inevitable in distributed systems. Service discovery improves resilience by routing traffic only to healthy service instances when failures occur.

3. Load Balancing

Most service discovery systems integrate with load balancing. Requests are automatically distributed across multiple instances of a service.

4. Decentralized Communication

Services communicate using logical names instead of physical addresses, such as:

5. Polyglot Support

Microservices are often built using multiple languages and frameworks. Service discovery works across platforms such as:

Types of Service Discovery

1. Client-Side Discovery

In client-side discovery:

Common tools include:

2. Server-Side Discovery

In server-side discovery:

Examples include:

Service Discovery in ASP.NET Core: Practical Options

In real-world .NET microservices, service discovery is commonly implemented using one of the following approaches.

Option 1: Kubernetes Built-in Service Discovery (Recommended)

If your services run inside Kubernetes, service discovery is provided out of the box.

Example:

If you create a Kubernetes service named:

payment-service

Other services can call it using:

http://payment-service

Kubernetes automatically routes traffic to healthy instances.

Option 2: Consul Service Discovery (VM or Docker Environments)

If you are not using Kubernetes, Consul is a popular choice for service discovery in ASP.NET Core applications.

Consul provides:

Implementing Service Discovery in ASP.NET Core Using Consul

Below is a simple and practical implementation example.

Step 1: Install the Consul NuGet Package

dotnet add package Consul

Step 2: Add Configuration in appsettings.json

{
  "Consul": {
    "Address": "http://localhost:8500",
    "ServiceName": "order-service",
    "ServiceId": "order-service-1",
    "ServiceAddress": "localhost",
    "ServicePort": 5001
  }
}

Step 3: Add a Health Check Endpoint

using Microsoft.AspNetCore.Mvc;

[ApiController]
public class HealthController : ControllerBase
{
    [HttpGet("/health")]
    public IActionResult Health()
    {
        return Ok("Healthy");
    }
}

Step 4: Register the Service in Program.cs

using Consul;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.MapControllers();

app.Lifetime.ApplicationStarted.Register(() =>
{
    var consulConfig = builder.Configuration.GetSection("Consul");

    var consulClient = new ConsulClient(c =>
    {
        c.Address = new Uri(consulConfig["Address"]);
    });

    var registration = new AgentServiceRegistration
    {
        ID = consulConfig["ServiceId"],
        Name = consulConfig["ServiceName"],
        Address = consulConfig["ServiceAddress"],
        Port = int.Parse(consulConfig["ServicePort"]),
        Check = new AgentServiceCheck
        {
            HTTP = $"http://{consulConfig["ServiceAddress"]}:{consulConfig["ServicePort"]}/health",
            Interval = TimeSpan.FromSeconds(10),
            Timeout = TimeSpan.FromSeconds(5)
        }
    };

    consulClient.Agent.ServiceRegister(registration).Wait();
});

app.Run();

Step 5: Run Consul

If Consul is running, open the Consul UI:

http://localhost:8500

Your microservice will now appear in the services list.

Conclusion

Service discovery is a foundational component of microservices architecture. Because microservices are dynamic and constantly changing, service discovery enables reliable communication without hardcoding network addresses.

For ASP.NET Core microservices, the most common service discovery approaches are:

Both approaches provide scalability, resilience, and load balancing support for modern distributed systems.