The concept of distributed applications is certainly not new. Whoever has a long IT career certainly remembers a number of different technologies implementing distributed components even in the early years. Nowadays, is all about microservices. They are a new form by which we consider today the concept of distributed computing.
Their peculiarity is that their communications are based essentially on REST and messaging protocols, which have the advantage of being widely spread standards. The core concept is essentially the same, having pieces of the whole system completely independent one from the other and running each in its own process.
The microservices world, coupled with the advent of cloud platforms has paved the way for a thriving of related technologies. This new architectural style has its drawbacks and a number of specific patterns are required to overcome them. Spring Cloud is a Spring project based on Spring Boot and contains specific packages that cover such patterns, both with its own solutions and integrating third-party ones as well (like Netflix OSS tools). In this article, we will show a list of the main microservices patterns and a brief overview of how Spring Cloud copes with them.
The present post is meant to be a quick introduction to the framework and the first of a series of articles aimed at covering the most important features and modules of Spring Cloud. In the next article, we will cover the basics of remote configuration, which is a fundamental piece of the Spring Cloud microservice ecosystem.
We can describe a monolithic application as a self-contained system, the goal is to solve a range of functionalities in a single processing unit. The following picture shows what a monolithic application could look like. The main concept is that all the application is decomposed into layers specialized in some general design logic, like business logic and database layers but all those layers run typically in the same process and communicate with each other by internal method calls (internal to the Java Virtual Machine in which they run).
Microservice applications have a more complex structure. We can think of a microservice system as an evolution of a monolithic one where its main features are separated into independent applications, each running in its own process, and possibly internally decomposed into layers like in the monolithic schema depicted above.
The following picture shows a rough example of what a microservice system could look like. It’s an oversimplified schema, but it serves the purpose of having a general understanding. In the picture, we have a gateway, which represents the entrance of the external world into the system, and some microservices, each in a separate node (hardware or virtual machine).
In the picture above, each microservice uses its own database instance running in a separate node as well. In reality, the way we deploy the single services does not follow rigid rules, we could have a single shared node to host the database or even a single node to host the three services each running in a separate process (we don’t talk here about containers just to keep things simple and generic).
Besides the specific deploy schema, the main difference compared to the monolithic scenario is that we have a number of features running in their own process and these are connected typically with REST or messaging protocols, that is to say, they communicate by remote calls and they are namely “distributed” components.
This “distributed” nature allows us to develop each piece of the system independently. This way we can enhance the reuse logic: it is simpler to devise clean and robust designs in such specialized components and the single pieces can be developed by completely independent teams. Unfortunately, this also comes with a number of drawbacks.
- How do we coordinate these distributed components?
- How do we deal with configuration in a centralized and consistent way?
To exploit this new software paradigm, we need technologies to cope with its main drawbacks. The advent of Cloud technologies has offered an enhanced and effective way of treating these concerns. This does not mean that microservices are a solution for each and every problem. Sometimes a monolithic solution would be the more natural choice. We can say that microservices could be an excellent choice for large and complex systems, but lose some of their appeal for simpler ones.
Spring Cloud, Microservices, and Netflix OSS
Spring Cloud sits on the Spring Boot framework. It offers a number of solutions on its own and integrates with external tools to cope with the main microservices architectural concerns. Netflix OSS is a series of software solutions that cover the main microservices patterns. Spring Cloud packages offer a layer of integration towards those solutions: it will be enough to use the related Spring Boot starter dependencies and some specific configurations.
Setting Dependencies as Release Trains
To simplify dependency management the concept of release train has been introduced. Spring Cloud is a collection of modules, each specialized on some specific feature, and each of them is developed independently. A release train identifies a set of modules releases that are verified as fully compatible with each other. Once we have chosen the Spring Boot version to use, we have to pick a Spring Cloud release train that is compatible with that version, and set it in a Maven dependency management section:
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2021.0.5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
Then we can set the specific module’s dependencies by Spring Cloud starters in the “dependencies section.” The dependency management section serves the only purpose of specifying the whole set of modules and related versions that we want to use. This way, we don’t have to specify any version for the individual module in the dependency section. This Maven feature is called BOM (Bill of Materials).
Following the above dependency management settings, if we want our application to use the features of the Spring Cloud Config module, we can simply add a piece of configuration like this:
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> </dependency> </dependencies>
To avoid searching for a compatibility matrix in the Spring documentation in setting up an application from scratch, a practical way would be to use the start.spring.io site. There you can first select the Spring Boot version and then the wanted Spring Cloud modules.
Spring Cloud and Microservice Patterns
We list here the main patterns involved in microservice architecture and what the Spring Cloud package offers for them:
- Distributed/versioned configuration
- Service registration and discovery
- Service-to-service calls
- Load balancing
- Circuit breakers
- Distributed messaging
An important concern with microservice architectures is how to deal with configuration. Since we have a number of services each running in its own process, we could simply think that each service is responsible for its own configuration. From the standpoint of system administration, this would be a nightmare. Spring Cloud provides its own solution to provide a centralized configuration feature and overcome this problem, named Spring Cloud Config. Spring Cloud Config uses Git as the first choice for a backend storing facility (an alternative would be Vault from Hashicorp). Two alternatives of Spring Cloud Config are Consul by Hashicorp and Apache ZooKeeper (both with features not strictly limited to distributed configuration).
Service Registration and Discovery
One of the main characteristics of microservices architecture is service registration and discovery. Each service registers itself to a central server (possibly distributed on more than one node for high availability) and uses that central server to find other services to communicate with. Spring Cloud provides integration with the Netflix OSS tool Eureka. Eureka has both a client and a server package. The client is provided by
spring-cloud-starter-eureka and the server by a
spring-cloud-starter-eureka-server dependency. A service that implements the client side will use the server to register itself and at the same time find other already registered services.
Distributed Logging and Tracing
Logging and tracing in microservices applications are not trivial tasks. We need to collect the logging activity in a centralized way and at the same time offer some advanced way of tracing service interactions across the whole system. Spring Cloud Sleuth library is an available choice to solve these problems. Sleuth’s more important feature is to associate all the secondary requests across microservices to the single input request that triggered them. So it collects all the logging involved by some identification information and at the same time provides a complete picture of the interactions involved in each request, including timing information. Such information can then be exported to another tool named Zipkin, which is specialized in analyzing latency problems in a microservices architecture.
To establish communication between the external world and our microservices system we need to route the incoming request to the right services. A Netflix OSS solution is Zuul, which can be used inside a Spring application through the
spring-cloud-starter-zuul starter dependency and related configuration. Zuul can play the role of an API gateway to the system and also work as a server-side load balancer.
The main form of communication between services is the REST protocol. Spring offers
RestTemplate as a synchronous client to perform REST calls (a recent asynchronous alternative is
WebClient). Spring Cloud also supports Netflix Feign as a REST-based client by the
spring-cloud-starter-feign starter dependency. Feign uses specific annotations that allow defining interfaces that will be implemented by the framework itself.
Load balancing is another important feature that microservices systems must implement. Different rules could be used, like a simple round robin, skipping servers that are overloaded, or using an algorithm based on the average response time. Spring Cloud can support load balancing by integrating Ribbon, a library from Netflix OSS.
A common scenario in a microservices system is the possibility that some service failure would affect the other services and the whole system. Circuit Breaker is a pattern that tries to solve this problem by defining a failure threshold after which the service interrupts immediately its execution and returns some form of predefined result. Hystrix is a library of Netflix OSS that implements this pattern. To include Hystrix in a Spring Cloud project the
spring-cloud-starter-hystrix Spring Boot starter must be used.
Besides the classic REST-style communications between services, we also have the option of using messaging architectural patterns. We can base our whole architecture, or just a part of it, on publish/subscribe or event-driven point-to-point messaging. Spring Cloud Stream package allows us to implement message-driven microservices and integrate the more popular message brokers like RabbitMQ and Apache Kafka.
Microservices architectures require a number of patterns to overcome their inherent complexity. Spring Cloud provides solutions to these patterns with its own implementations and integration with third-party tools as well. In this article, we have made a quick overview of the main modules of Spring Cloud. In the next article, we cover the basics of the Spring Cloud Config module.
We have also covered some important features of Spring Boot, which is the base platform of Spring Cloud, in the following articles: