Microservicesallow large systems to be built up from a number of collaborating components. It does at the process level what Spring has always done at the component level: loosely coupled processes instead of loosely coupled components.
Inevitably there are a number of moving parts that you have to setup and configure to build such a system. How to get them working together is not obvious - you need to have good familiarity with Spring Boot since Spring Cloud leverages it heavily, several Netflix or other OSS projects are required and, of course, there is some Spring configuration "magic"!
When you have multiple processes working together they need to find each other. If you have ever used Java's RMI mechanism you may recall that it relied on a central registry so that RMI processes could find each other. Microservices has the same requirement.
The developers at Netflix had this problem when building their systems and created a registration server called Eureka ("I have found it" in Greek). Fortunately for us, they made their discovery server open-source and Spring has incorporated into Spring Cloud, making it even easier to run up a Eureka server. Here is the complete discovery-server application:
By default Spring Boot applications look for an application.properties or application.yml file for configuration. By setting the
spring.config.name property we can tell Spring Boot to look for a different file - useful if you have multiple Spring Boot applications in the same project - as I will do shortly.
By default Eureka runs on port 8761, but here we will use port 1111 instead. Also by including the registration code in my process I might be a server or a client. The configuration specifies that I am not a client and stops the server process trying to register with itself.
When configuring applications with Spring we emphasize Loose Coupling and Tight Cohesion, These are not new concepts (Larry Constantine is credited with first defining these in the late 1960s - reference) but now we are applying them, not to interacting components (Spring Beans), but to interacting processes.
In this example, I have a simple Account management microservice that uses Spring Data to implement a JPA AccountRepository and Spring REST to provide a RESTful interface to account information. In most respects this is a straightforward Spring Boot application.
Run the AccountsService application now and let it finish initializing. Refresh the dashboard :1111 and you should see the ACCOUNTS-SERVICE listed under Applications. Registration takes up to 30 seconds (by default) so be patient - check the log output from RegistrationService
Registration Time: Registration takes up to 30s because that is the default client refresh time. You can change this by setting the eureka.instance.leaseRenewalIntervalInSeconds property to a smaller number (in the demo application I have set it to 5). This is not recommended in production. See also.
Registration Id: A process (microservice) registers with the discovery-service using a unique id. If another process registers with the same id, it is treated as a restart (for example some sort of failover or recovery) and the first process registration is discarded. This gives us the fault-tolerant system we desire.
To run multiple instances of the same process (for load-balancing and resilience) they need to register with a unique id. When I first wrote this blog, that was automatic and since the Brixton release-train, it is again.
Under the Angel release train, the instance-id, used by a client to register with a discovery server, was derived from the client's service name (the same as the Spring application name) and also the client's host name. The same processes running on the same host would therefore have the same id, so only one could ever register.
We are setting the instanceId to application-name:instance_id, but if instance_id is not defined, we will use application-name::server-port instead. Note that the spring.application.instance_id is only set when using Cloud Foundry but it conveniently provides a unique id number for each instance of the same application. We can do something similar when running elsewhere by using the server-port (since different instances on the same machine must listen on different ports. Another example you will often see is $spring.application.name:$spring.application.instance_id:$random.value but I personally find using the port number makes each instance easy to identify - the random values are just long strings that don't mean anything.
Which formats can be used depends on the presence of marshaling classes on the classpath - for example JAXB is always detected since it is a standard part of Java. JSON is supported if Jackson jars are present in the classpath.
The RestTemplate bean will be intercepted and auto-configured by Spring Cloud (due to the @LoadBalanced annotation) to use a custom HttpRequestClient that uses Netflix Ribbon to do the microservice lookup. Ribbon is also a load-balancer so if you have multiple instances of a service available, it picks one for you. (Neither Eureka nor Consul on their own perform load-balancing so we use Ribbon to do it instead).
Note: From the Brixton Release Train (Spring Cloud 1.1.0.RELEASE), the RestTemplate is no longer created automatically. Originally it was created for you, which caused confusion and potential conflicts (sometimes Spring can be too helpful!).
Note that this instance is qualified using @LoadBalanced. (The annotation is itself annotated with @Qualifier - see here for details). Thus if you have more than one RestTemplate bean, you can make sure to inject the right one, like this:
A RestTemplate instance is thread-safe and can be used to access any number of services in different parts of your application (for example, I might have a CustomerService wrapping the same RestTemplate instance accessing a customer data microservice).
The Eureka dashboard (inside RegistrationServer) is implemented using FreeMarker templates but the other two applications use Thymeleaf. To make sure each uses the right view engine, there is extra configuration in each YML file.
As mentioned above, Spring Boot applications look for either application.properties or application.yml to configure themselves. Since all three servers used in this application are in the same project, they would automatically use the same configuration.
Spring Boot sets up INFO level logging for Spring by default. Since we need to examine the logs for evidence of our microservices working, I have raised the level to WARN to reduce the amount of logging.
To do this, the logging level would need to be specified in each of the xxxx-server.yml configuration files. This is usually the best place to define them as logging properties cannot be specified in property files (logging has already been initialized before @PropertySource directives are processed). There is a note on this in the Spring Boot manual, but it's easy to miss.
Rather than duplicate the logging configuration in each YAML file, I instead opted to put it in the logback configuration file, since Spring Boot uses logback - see src/main/resources/logback.xml. All three services will share the same logback.xml.
Adopting a microservices architecture provides unique opportunities to add failover and resiliency to your systems so your components can gracefully handle load spikes and errors. Microservices make change less expensive, too. They can also be a good idea when you have a large team working on a single product. You can break up your project into components that can function independently. Once components can operate independently, they can be built, tested, and deployed separately. This gives an organization and its teams the agility to develop and deploy quickly.
Java is an excellent language with a vast open source ecosystem for developing a microservice architecture. In fact, some of the biggest names in our industry use Java and contribute to its ecosystem. Have you ever heard of Netflix, Amazon, or Google? What about eBay, Twitter, and LinkedIn? Yes, web-scale companies handling incredible traffic are doing it with Java.
Implementing a microservices architecture in Java isn't for everyone. For that matter, implementing microservices, in general, isn't often needed. Most companies do it to scale their people, not their systems. Even Martin Fowler's original blog post on Microservices recommends against it:
One reasonable argument we've heard is that you shouldn't start with a microservices architecture. Instead begin with a monolith, keep it modular, and split it into microservices once the monolith becomes a problem.
The Java ecosystem has some well-established patterns for developing microservice architectures. If you're familiar with Spring, you'll feel right at home developing with Spring Boot and Spring Cloud. Since that's one of the quickest ways to get started, I figured I'd walk you through a quick example.
This example contains a microservice with a REST API that returns a list of cool cars. It uses Netflix Eureka for service discovery, WebClient for remote communication, and Spring Cloud Gateway to route requests to the microservice. It integrates Spring Security and OAuth 2.0, so only authenticated users can access the API gateway and microservice. It also uses Resilience4j to add fault tolerance to the gateway.
I like to show developers how to build everything from scratch. Today, I'm going to take a different approach. First, I'll show you how to get the completed example working. Then, I'll explain how I created everything and the trials and tribulations I encountered along the way.
To run the example, you must install the Auth0 CLI and create an Auth0 account. If you don't have an Auth0 account, sign up for free. I recommend using SDKMAN! to install Java 17+ and HTTPie for making HTTP requests.
A few years ago, I created an example similar to this one with Spring Boot 2.2. It used Feign for remote connectivity, Zuul for routing, Hystrix for failover, and Spring Security for OAuth. The September 2023 version of Spring Cloud has Spring Cloud OpenFeign for remote connectivity, Spring Cloud Gateway for routing, and Resilience4j for fault tolerance.
3a8082e126