With monolithic applications, services invoke one another through language-level methods or procedure calls. This is relatively straightforward and predictable behavior, but as application complexity increased over time, monolithic applications became unsuitable for the scale and demand of modern software systems. This resulted in a shift toward SOA, or service-oriented architecture. The monoliths were broken into smaller chunks that typically served a particular purpose.
SOA brought its own caveats into the picture with inter-service calls. SOA services ran at well-known fixed locations, which resulted in the static location of services, IP addresses, and reconfiguration issues with deployments, among other things.
Building Microservice Systems Is Difficult
With microservices, the application typically runs in a virtual or containerized environment, within which the number of instances of a service, and the locations of those services, can change dynamically and frequently. This gives us the ability to scale our application depending on the forces dynamically applied to it, but this flexibility doesn’t come without its own share of problems. One of the main issues lies in not knowing where your services are to contact them. Without the right patterns, it can almost be impossible.
With service discovery, the services register with the dynamic service registry upon startup. In addition to the IP address and port on which they’re running, it’ll also often provide metadata such as service version or other environmental parameters that a client can use when querying the registry.
[bctt tweet=”When #Microservices are poorly designed, inter-service calls and shifting service locations within container clusters can cause issues. Service discovery tools that manage dynamic service registries can address the problem. || #IoTForAll #IoT @coolsvap ” username=”iotforall”]
Some of the popular examples of service registries are Consul and Etcd. These systems are highly scalable and have rigorously consistent methods for storing the location of your services. In addition to this, Consul can perform health checks on a service to ensure its availability and integrity. If the service fails a health check, it’s marked as unavailable in the registry and will not be returned by any queries.
There are two main patterns of service discovery: server-side and client-side.
Server-Side Service Discovery
Server-side service discovery is a microservice antipattern for inter-service calls within the same application. This is the method we use to call services in an SOA environment. Typically, there will be a reverse proxy that acts as a gateway to your services. It contacts the dynamic service registry and forwards your request on to the back-end services. The client then accesses the back-end services by implementing a known URL using either a sub-domain or a path as a differentiator.
Eventually, server-side discovery will run into some well-known issues, one of them being a reverse proxy bottleneck. The back-end services can be scaled quickly enough, but it requires monitoring. It also introduces latency, causing an increase in cost for running and maintaining the application.
Server-side discovery also potentially increases failure patterns with downstream calls, internal services, and external services. With server-side discovery, you need to have a centralized failure logic on the server-side, which abstracts most API knowledge from the client, handles failures internally, and keeps retrying internally while keeping the client completely distant until it’s a success or a catastrophic failure.
Client-Side Service Discovery
Server-side service discovery might be an acceptable choice for your public APIs for any internal inter-service communication. However, I prefer the client-side pattern. This gives you greater control over what happens when a failure occurs. You can implement the business logic on a retry of a failure on a case-by-case basis. This will also protect you against cascading failure.
This pattern is similar to its server-side partner. However, the client is responsible for the service discovery and load balancing. A dynamic service registry is still needed to get the information for the services you are going to call. This logic is localized in each client, so it is possible to handle the failure logic on a case-by-case basis.