Building Microservices On .NET Core – Part 3 Service Discovery with Eureka

20/02/2019

This is the third article in our series about building microservices on .NET Core. In the first article we introduced the series and prepared the plan: business case and solution architecture. In the second article we described how you can structure internal architecture of one microservice using CQRS pattern and MediatR library.

In this article we are going to focus on service discovery, which is one of the fundamental concepts of microservice based architecture.

Source code for complete solution can be found on our GitHub.

 

What is service discovery and do I need one?

If you are building microservice based solution sooner or later (rather sooner) you will encounter a situation when one service need to talk to another. In order to do this caller must know the exact location of a target microservice in a network they operate in.
You must somehow provide IP address and port where target microservice listens for requests. You can do it using configuration file or environment variables, but this approach has some drawbacks and limitations.

First is that you have to maintain and properly deploy configuration files for all your environments: local development, test, pre-production and production. Forgetting to update any of these configurations when adding a new service or moving existing one to a different node will result in errors discovered at runtime.
Second, more important issue, is that it works only in static environment, meaning you cannot dynamically add/remove nodes, therefore you won’t be able to dynamically scale your system. Ability to scale and deploy given microservice autonomously is one of key advantages of microservice based architecture, and we do not want to lose this ability.

Therefore we need to introduce service discovery. Service discovery is a mechanism that allows services to find each others network location. There are many possible implementations of this pattern, but in this article we will focus on implementation that consist of Service Registry component and Service Registry Clients.
Service Registry is a central component that maintains list of microservices instances currently running with their corresponding network locations. Service Registry Clients are used by your microservices to: register itself in registry, to query registry for address of a given microservices they need to communicate with.

There are many existing implementations of service registry available. Unfortunately I do not know of any native .NET solution. In Java world there are two solutions we use at Altkom Software & Consulting in many projects: Netflix Eureka and HashiCorp Consul.

For the purpose of this article we will use Eureka as it has a very good client library for .NET Core developed and maintained by Pivotal (creators of Spring Framework) – Steeltoe.

 

Setting up a Service Registry with Eureka

The simplest way to start Eureka Server is clone a GitHub repository and run with Maven wrapper which is attached to this project. This allows you to run the Maven project without having Maven installed and present on the path.

We cloned the repository to the eureka folder, so in our case, it is enough:

cd eureka
mvnw spring-boot:run

Eureka Server configuration is stored in a file application.yml.

server:
  port: 8761

eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false
  server:
    waitTimeInMsWhenSyncEmpty: 0

If you want to check that Eureka started correctly, go to localhost:8761. You should show something like this:

spring Eureka

 

Registering microservice in Eureka / Register Eureka Clients

We used Steeltoe’s Eureka client implementation to register and fetch services from Eureka Server. In this section we focus on registration in service registry. We do it all on the example of one of our microservices – PricingService.

First step is adding required NuGet packages.

dotnet add package Steeltoe.Discovery.ClientCore --version 2.1.1

Second step is adding discovery client in the Startup.cs class. Here we must add two lines, one to ConfigureServices method and second to Configure method.

using Steeltoe.Discovery.Client;

namespace PricingService
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDiscoveryClient(Configuration);
            [...]
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
           [...]
            app.UseDiscoveryClient();
        }
    }
}

Last step and the most important one is setting up configuration in appsettings.json

"spring" : {
    "application" : {
      "name" : "PricingService"
    }
  },
  "eureka" : {
    "client" : {
      "shouldRegisterWithEureka" : true,
      "serviceUrl" : "http://localhost:8761/eureka",
      "ValidateCertificates":  false
    },
    "instance" : {
      "appName" : "PricingService",
      "hostName" : "localhost",
      "port" : "5040”
    }
  }

Configuration consist of the following elements:

At first I was a bit disappointed that I have to specify host name and port my service is using instead of Eureka client being able to dynamically discover this information at runtime, but it turn out to be a very useful feature.
While running your services in a docker container we do not want them to register ports and addresses local to container instance but addresses and ports as visible on a docker network.
More information concerning configuration options can be found in Steeltoe docs.

Now we can run our microservice using, for example, command line. From root repository folder:

dotnet run --project ./PricingService

and open Eureka to see if your service is visible. Open your browser and go to localhost:8761

eureca

If everything worked as expected, an instance of your service should be listed in the “Instances currently registered with Eureka” section.

You can create a health check endpoint so that Eureka could check if you service is up and running. This would enable almost real-time monitoring of available services and their status via Eureka dashboard.
If you implement such endpoint you can specify it using healthCheckUrl configuration property.

 

Call another microservice using service discovery

Our first step is adding Eureka client configuration in the same way as we did for PricingService.

After previous steps we are ready to call our service from another one. We want to call PricingService from PolicyService, because on one of policy creation step we need some data about price that are in PricingService.
We are going to combine forces Steeltoe discovery client, RestEase and Polly to create a nice, declarative and resilient client for our PricingService.

using Microsoft.Extensions.Configuration;
using Polly;
using PricingService.Api.Commands;
using RestEase;
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Steeltoe.Common.Discovery;

namespace PolicyService.RestClients
{
    public interface IPricingClient
    {
        [Post]
        Task CalculatePrice([Body] CalculatePriceCommand cmd);
    }

    public class PricingClient : IPricingClient
    {
        private readonly IPricingClient client;

        private static Policy retryPolicy = Policy
            .Handle()
            .WaitAndRetryAsync(retryCount: 3, sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(3));

        public PricingClient(IConfiguration configuration, IDiscoveryClient discoveryClient)
        {
            var handler = new DiscoveryHttpClientHandler(discoveryClient);
            var httpClient = new HttpClient(handler, false)
            {
                BaseAddress = new Uri(configuration.GetValue("PricingServiceUri"))
            };
            client = RestClient.For(httpClient);
        }

        public Task CalculatePrice([Body] CalculatePriceCommand cmd)
        {
            return retryPolicy.ExecuteAsync(async () => await client.CalculatePrice(cmd));
        }
    }
}

We declared an interface that represents operations exposed by PricingService.

We created implementation of this interface that internally uses service discovery client that will fetch address of PricingService from service registry in Eureka, combined with Polly and RestEase.

The most important lines from this example is handler and HTTP client creation:

var handler = new DiscoveryHttpClientHandler(discoveryClient);
var httpClient = new HttpClient(handler, false)
{
    BaseAddress = new Uri(configuration.GetValue("PricingServiceUri"))
};

We use the URL to PricingService configured in appsettings.json and looks like this:

"PricingServiceUri" : "http://PricingService/api/pricing"

As you can see address in configuration points to service name not to actual network address.

Last step is registration of our client in IoC container. For this purpose we create a class called RestClientsInstaller.

    public static class RestClientsInstaller
    {
        public static IServiceCollection AddPricingRestClient(this IServiceCollection services)
        {
            services.AddSingleton(typeof(IPricingClient), typeof(PricingClient));
...
            return services;
        }
    }

and we use this class in our Startup.cs

public void ConfigureServices(IServiceCollection services)
        {
            services.AddDiscoveryClient(Configuration);
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            services.AddMediatR();
            services.AddPricingRestClient();
            services.AddNHibernate(Configuration.GetConnectionString("DefaultConnection"));
            services.AddRabbit();
        }

Now you can inject IPricingClient and use it to call PricingService.

 

Summary

Service discovery is one of the fundamentals of microservices based architecture. Without it there is a lot of manual and error prone work required to properly configure and deploy microservices.

Setting up service discovery with Eureka and Steeltoe is pretty easy and gives you ability to dynamically add and remove service instances. Also it removes the need to hard code addresses of services that need to communicate with.
Check out complete solution source code at: https://github.com/asc-lab/dotnetcore-microservices-poc.

Of course, this solution is just one of the possible ways of implementation service discovery mechanism. You can also try alternative approaches with Kubernetes, nginx or Consul.

Authors:

Wojciech Suwała, Head Architect, ASC LAB

Robert Witkowski, Senior Software Engineer, ASC LAB

[ratings]

Zarejestruj się na webinar:

Wyrażam dobrowolnie zgodę na przetwarzanie moich danych osobowych dla potrzeb procesu realizacji zgłoszenia (pokaż więcej) przez Altkom Experts sp. z o.o. oraz Altkom Software & Consulting sp. z o.o. (ul. Chłodna 51, 00-867 Warszawa), zgodnie z Rozporządzeniem Parlamentu Europejskiego i Rady (UE) 2016/679 z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych („RODO”). Mam świadomość, iż podanie powyższych danych osobowych jest dobrowolne, ale konieczne do obsługi zgłoszenia oraz że posiadam prawo dostępu do treści swoich danych i możliwość ich poprawiania a zgoda może być odwołana w każdym czasie. Kontakt do inspektora ochrony danych osobowych w Altkom: iodo@altkom.pl

Wyrażam dobrowolnie zgodę na przetwarzanie moich danych osobowych w celach marketingowych (pokaż więcej)przez Altkom Experts sp. z o.o. oraz Altkom Software & Consulting sp. z o.o. (ul. Chłodna 51, 00-867 Warszawa)oraz otrzymywanie informacji handlowych drogą elektroniczną na adres e-mail i numer telefonu podany przeze mnie, zgodnie z Rozporządzeniem Parlamentu Europejskiego i Rady (UE) 2016/679 z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych („RODO”), oraz Ustawy z dnia 18 lipca 2002 r. o świadczeniu usług drogą elektroniczną. Mam świadomość, iż podanie powyższych danych osobowych jest dobrowolne oraz że posiadam prawo dostępu do treści swoich danych i możliwość ich poprawiania a zgoda może być odwołana w każdym czasie. Kontakt do inspektora ochrony danych osobowych w Altkom: iodo@altkom.pl

 

Watch for free

Enter your contact details to receive a link to the recording:

I voluntarily consent to the processing of my personal data for the purposes of the application process (show more) by Altkom Experts sp. z o.o. and Altkom Software & Consulting sp. z o.o. (51 Chłodna Street, 00-867 Warsaw), in accordance with Regulation 2016/679 of the European Parliament and of the Council of 27 April 2016 on the protection of individuals with regard to the processing of personal data and on the free movement of such data ("RoDO"). I am aware that providing the above personal data is voluntary, but necessary to handle the request, and that I have the right to access the content of my data and the possibility of correcting it, and that my consent may be revoked at any time. Please contact the Altkom Data Protection Officer: iodo@altkom.pl.

I voluntarily consent to the processing of my personal data for marketing purposes (show more)by Altkom Experts sp. z o.o. and Altkom Software & Consulting sp. z o.o. (ul. Chłodna 51, 00-867 Warsaw) and receiving commercial information by electronic means to the e-mail address and telephone number provided by me, in accordance with Regulation 2016/679 of the European Parliament and of the Council of 27 April 2016 on the protection of individuals with regard to the processing of personal data and on the free movement of such data ("RODO"), and the Act of 18 July 2002 on the provision of electronic services. I am aware that providing the above personal data is voluntary and that I have the right to access and correct my data and that my consent may be revoked at any time. Please contact the Altkom Data Protection Officer: iodo@altkom.pl.

 

Zarejestruj się na webinar:

Wyrażam dobrowolnie zgodę na przetwarzanie moich danych osobowych dla potrzeb procesu realizacji zgłoszenia (pokaż więcej) przez Altkom Experts sp. z o.o. oraz Altkom Software & Consulting sp. z o.o. (ul. Chłodna 51, 00-867 Warszawa), zgodnie z Rozporządzeniem Parlamentu Europejskiego i Rady (UE) 2016/679 z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych („RODO”). Mam świadomość, iż podanie powyższych danych osobowych jest dobrowolne, ale konieczne do obsługi zgłoszenia oraz że posiadam prawo dostępu do treści swoich danych i możliwość ich poprawiania a zgoda może być odwołana w każdym czasie. Kontakt do inspektora ochrony danych osobowych w Altkom: iodo@altkom.pl

Wyrażam dobrowolnie zgodę na przetwarzanie moich danych osobowych w celach marketingowych (pokaż więcej)przez Altkom Experts sp. z o.o. oraz Altkom Software & Consulting sp. z o.o. (ul. Chłodna 51, 00-867 Warszawa)oraz otrzymywanie informacji handlowych drogą elektroniczną na adres e-mail i numer telefonu podany przeze mnie, zgodnie z Rozporządzeniem Parlamentu Europejskiego i Rady (UE) 2016/679 z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych („RODO”), oraz Ustawy z dnia 18 lipca 2002 r. o świadczeniu usług drogą elektroniczną. Mam świadomość, iż podanie powyższych danych osobowych jest dobrowolne oraz że posiadam prawo dostępu do treści swoich danych i możliwość ich poprawiania a zgoda może być odwołana w każdym czasie. Kontakt do inspektora ochrony danych osobowych w Altkom: iodo@altkom.pl

 

Zarejestruj się na webinar:

Wyrażam dobrowolnie zgodę na przetwarzanie moich danych osobowych dla potrzeb procesu realizacji zgłoszenia (pokaż więcej) przez Altkom Experts sp. z o.o. oraz Altkom Software & Consulting sp. z o.o. (ul. Chłodna 51, 00-867 Warszawa), zgodnie z Rozporządzeniem Parlamentu Europejskiego i Rady (UE) 2016/679 z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych („RODO”). Mam świadomość, iż podanie powyższych danych osobowych jest dobrowolne, ale konieczne do obsługi zgłoszenia oraz że posiadam prawo dostępu do treści swoich danych i możliwość ich poprawiania a zgoda może być odwołana w każdym czasie. Kontakt do inspektora ochrony danych osobowych w Altkom: iodo@altkom.pl

Wyrażam dobrowolnie zgodę na przetwarzanie moich danych osobowych w celach marketingowych (pokaż więcej)przez Altkom Experts sp. z o.o. oraz Altkom Software & Consulting sp. z o.o. (ul. Chłodna 51, 00-867 Warszawa)oraz otrzymywanie informacji handlowych drogą elektroniczną na adres e-mail i numer telefonu podany przeze mnie, zgodnie z Rozporządzeniem Parlamentu Europejskiego i Rady (UE) 2016/679 z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych („RODO”), oraz Ustawy z dnia 18 lipca 2002 r. o świadczeniu usług drogą elektroniczną. Mam świadomość, iż podanie powyższych danych osobowych jest dobrowolne oraz że posiadam prawo dostępu do treści swoich danych i możliwość ich poprawiania a zgoda może być odwołana w każdym czasie. Kontakt do inspektora ochrony danych osobowych w Altkom: iodo@altkom.pl

 

Zarejestruj się na webinar:

Wyrażam dobrowolnie zgodę na przetwarzanie moich danych osobowych dla potrzeb procesu realizacji zgłoszenia (pokaż więcej) przez Altkom Experts sp. z o.o. oraz Altkom Software & Consulting sp. z o.o. (ul. Chłodna 51, 00-867 Warszawa), zgodnie z Rozporządzeniem Parlamentu Europejskiego i Rady (UE) 2016/679 z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych („RODO”). Mam świadomość, iż podanie powyższych danych osobowych jest dobrowolne, ale konieczne do obsługi zgłoszenia oraz że posiadam prawo dostępu do treści swoich danych i możliwość ich poprawiania a zgoda może być odwołana w każdym czasie. Kontakt do inspektora ochrony danych osobowych w Altkom: iodo@altkom.pl

Wyrażam dobrowolnie zgodę na przetwarzanie moich danych osobowych w celach marketingowych (pokaż więcej)przez Altkom Experts sp. z o.o. oraz Altkom Software & Consulting sp. z o.o. (ul. Chłodna 51, 00-867 Warszawa)oraz otrzymywanie informacji handlowych drogą elektroniczną na adres e-mail i numer telefonu podany przeze mnie, zgodnie z Rozporządzeniem Parlamentu Europejskiego i Rady (UE) 2016/679 z dnia 27 kwietnia 2016 r. w sprawie ochrony osób fizycznych w związku z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich danych („RODO”), oraz Ustawy z dnia 18 lipca 2002 r. o świadczeniu usług drogą elektroniczną. Mam świadomość, iż podanie powyższych danych osobowych jest dobrowolne oraz że posiadam prawo dostępu do treści swoich danych i możliwość ich poprawiania a zgoda może być odwołana w każdym czasie. Kontakt do inspektora ochrony danych osobowych w Altkom: iodo@altkom.pl