Back when I embarked on my Domain Driven Design/Microservice/Distributed systems journey I came across this neat little library to make REST calls between my services for MedPark, a sample distributed system project I am using to implement everything new I learn that is DDD related. I wrote a post on this library back when I discovered it here.
While RestEase works great at what it does, once you start doing distributed systems in every sense of the word it becomes a little trickier. When having multiple instances of an application running, having a hardcoded endpoint for a particular service in your configuration to send requests to kills the purpose of scaling, doesn’t it? For this, we can implement some kind of service discovery.
Service Discovery has the ability to locate a network automatically making it so that there is no need for a long configuration set up process. Service discovery works by devices connecting through a common language on the network allowing devices or services to connect without any manual intervention. (i.e Kubernetes service discovery, AWS service discovery)
There are two types of service discovery: Server-side and Client-side. Server-side service discovery allows clients applications to find services through a router or a load balancer. Client-side service discovery allows clients applications to find services by looking through or querying a service registry, in which service instances and endpoints are all within the service registry.
Let’s say we want to query the Catalog service and find out if a particular product is in stock. We would send this request to our API gateway and from the API we would relay that query to the Catalog service. When relaying this request, we could look up the Catalog service in some kind registry and send our request to any of the instances of the Catalog service that is healthy and available.
Consul, by HashiCorp, is a centralized service registry that enables services to discover each other by storing location information (like IP addresses) in a single registry. We will be using this service for looking up our services in a registry when communicating between services.
For local development and testing, we are using the Consul Docker image.
docker run consul -p 8500:8500
Above are two extension methods that I am using to register my service to Consul registry. The
AddConsul method adds the Consul Client and its options to the service collection, along with a custom HTTP client that will be used to make our requests between services. The
UseConsul method is how we will register our application to the Consul services registry. This requires four parameters:
|Name||The name of the service (i.e. Catalog-service)|
|ID||The ID of the service. Usually, the name of the service with a unique Id (Guid) appended to it.|
|Address||The address where the service will be running at|
|Port||The port the service will be running at|
Once we have our services registered on Consul all we need to do to request data between services are the service names. As is displayed in the screenshot above, we have 7 instances of the Basket service registered. When making our request from the API gateway to the Basket service, the API gateway does not have to know which instance to request. All we want is for Consul to send us in the direction of an instance that is healthy. Let’s look at how we can accomplish this.
I implemented an HTTP client to get the service we want to request from the Consul Service Registry and send our Request along. See an overview of the client implementation below:
To better explain the code in the gist above, say we want to get a customer’s basket from the Basket service, all we need to do is pass the name of the service along to this Consul HTTP client like this basket-service. In the code above, we ask Consul for all the services that are registered with that name, then we select a random instance from the registry to send the request to. This is how we could call the basket service from our controller:
The gists on this post have been chopped and screwed in a way to accommodate this article because some of it was not within the scope of giving you a high-level introduction to implementing Service discovery in your microservices-based application. You can have a look at the MedPark repository for the full implementation, which also uses HealthChecks for monitoring the healthiness of a service and then deregistering the service if it is unable to handle requests.
In closing, implementing service discovery in your distributed system’s architecture reduces the technical complexity of discovering and most importantly (for me anyway) automating the process for connected services.
Thank you for reading.