There are multiple ways to implement microservices, but this article will focus on building microservices with Spring Boot & Netflix OSS.
Microservices are all the rage right now and multiple projects have switched to using them. And, although, microservices are not the right fit for every use case – if they suit your project’s needs, then by all means use them!
So, put a pot of coffee on and get comfy! Because, this is an in-depth, comprehensive outline to help developers building microservices with Spring Boot and Netflix OSS.
Here’s an outline of all the elements I’ll be delving into:
1. Monoliths vs. Microservices
2. Netflix Microservices Tools & Code Example
3. Discovery Service
4. Review Service
5. Movie Service
– Feign Client
– Hystrix Circuit Breaker
6. API Gateway
7. Conclusion: Building Microservices with Spring Boot is Like Building Any Other Application – with Some Added Sugar
1. Monoliths vs. Microservices
Netflix was an early adopter of microservices. Way back in 2009, the company started their transition from a traditional development model with 100 engineers producing a monolithic DVD rental application – to a microservices architecture with multiple small teams taking up the end to end development of hundreds of microservices in tandem. They did all this to keep up with their booming demand. This resulted in Netflix engineers deploying code thousands of times a day.
When and if a microservice breaks, developers intervene to fix it without affecting the integrity of the entire system.
Most web applications are generally structured around a few components – controller, business-logic, & database. Even with a clearly structured modular architecture, you can’t split the application deployment. So, it must be packaged and deployed as a whole. In the case of microservices, everything can be divided in the smallest part possible – independently packaged then deployed.
Monoliths’ Advantages
- Easy Development
- Easy Testing, with everything you need in one package – from UI to database.
- Easy Deployment, all you have to do is package the entire project & deploy one binary file
- Easy to scale horizontally, by adding multiple instances on multiple machines.
Monoliths’ Disadvantages
- The application can become very large, very quickly – and it can become difficult for a developer to maintain a mental image of what the application is doing.
- The size of the application can slow down start-up times.
- On each iteration you must deploy the entire application, even if there are changes only in a small part of the app.
- Every change can introduce more bugs. The impact is not fully understood because of the fuzzy mental image.
- Reliability – a breaking bug in one part of the application can bring down the entire app and not just one functionality.
- As the project becomes larger and the team increases, making it sometimes hard for developers to work independently & without conflict.
- Monoliths are not flexible in terms of team diversity or technologies used and are skeptical about adopting new technologies or updating libraries since any change will affect the entire application at tremendous costs.
Microservices Architecture comes with the ability to split the monolith into small and interconnected services – with their own logical structure and technology freedom.
The most notable difference between monoliths and microservices lies in the database layer. Microservices suggest that every service has its own database instead of sharing one. This is a great thing for a service because it can use a database provider that best suits its needs. But, it can also result in data duplication.
Microservices rely heavily on the gateway pattern – responsible for monitoring, metering, authentication, load balancing, forwarding, etc. So, the end users (mobile app, desktop app, web app) don’t directly call the service. Instead, they go through an entry point.
Microservices’ Advantages
- Reduced complexity of an application by dividing it into smaller problems. (Divide et Impera)
- Calls for independent, flexible teams free to choose the best technology stack that best suits them and the product.
- Easily integrated with CI/CD environments.
- Can be scaled independently. So, if the business needs just one feature to be scaled, microservices can scale just that part – not the entire monolith.
- When a bug occurs, it will affect just one or a few systems – instead of the entire application.
Microservices’ Disadvantages:
- Added complexity with inter-service communication, separated database pattern & testing.
- Harder Database transactions, because changes need to be made in databases owned by multiple services.
- Testing can be more difficult.
- Harder to implement changes across multiple modules because of coordination across multiple teams.
- Deploying is harder because there are multiple binaries, but this process is made easier with modern cloud infrastructures.
2. Netflix Microservices Tools & Code Example
Now, let’s see how microservices are created and how they interact with each other using spring-boot, spring-cloud, and Netflix OSS to implement a minimalist microservices demo application.
There are two core services – movie-service and review-service, that will implement a simple movie library and a review system holding reviews for movies.
Beside the core services, there are also two more – discovery-service and api-gateway.
The discovery-service is a server that registers every service in our system. It allows us to call endpoints from another service within our cluster by service name instead of URL.
The API-gateway implements the front door of our application, a single-entry point exposed to the exterior that forwards requests to the back-end system.
We’ll be using Spring Cloud Netflix – which integrates Netflix OSS with Spring Boot, enabling auto-configuration for technologies found in Netflix stack.
3. Discovery Service
To keep this example as straightforward as possible, let’s use just two microservices in our demo. Keep in mind that in a real project there may be tens or even hundreds of independent services and all need to communicate with one another to share data and events.
In a monolithic application, this communication was simply solved by a method call to another module. But, in a microservice environment we need to make an API call to another service.
Easy peasy, you say? Just use the other service URL and fetch the data with RestTemplate. While that works fine in this demo, what happens when you have 100+ services – all deployed in a cloud environment, all with at least two running instances on multiple machines and dynamically assigned IPs?
Suddenly, this becomes a lot more complicated because we can’t simply hardcode the service URL in our code. This is where a discovery service comes in handy. It works like this – every service has a unique name assigned in the project, through spring application name property. When a service marked as a discovery client starts, it gets registered with the Server Registry that keeps track of every service in our environment. And, when we want to call another service, we just use the service name. The discovery server is responsible for calling the appropriate host-name and port for that server.
The implementation is nothing more than a simple spring boot web application. Make sure to define the spring-cloud dependency management and release train. This way, you can ensure every spring-cloud dependency you’re using is on the same cloud version and is compatible.
Then, add the eureka server dependency to the pom.xml
…and enable the server configuration in the spring boot application.
Because this is the discovery server, we need to specify in application properties that this service doesn’t need to register with the discovery server itself. We do that by setting the `eureka.client.register-with-eureka` flag to false. The 8761 port is the default port for the Eureka server. As you can see, this is the beauty of Spring Cloud Netflix. It makes it so much easier to configure a Netflix OSS technology in Spring Boot using annotations and configuration properties.
After configuring and running this service, if you access http://localhost:8761/ , you will see the eureka dashboard. At this point, we don’t have any service registered with Eureka, so the Applications list is empty. But in the General Info section, we can see some general metrics like available memory, memory usage, instance details, among others.
4. Review Service
The `review-service` provides a REST API that allows CRUD operations on reviews of movies. It’s minimalist in form, but the focus is not on the complexity of the project. The focus is in the architecture and how to connect services. When the service runs, a default set of reviews is created and saved in an H2 database.
To register with the eureka discovery client, we need to enable the configuration in our spring application.
Then, we set a unique application name in our cluster. We can also let spring assign a random port (using the 0 value for the port) for our service to show that it doesn’t matter because the host and port will be fetched from discovery client and we will use the name to access the service.
5. Movie Service
The same goes for the movie service. We have an ApplicationRunner bean to initialize our H2 database at start-up with some movies.
Then, we enable the configuration in our spring boot application and assign a unique port and name in the properties file.
After starting the movie and review service, if we access Eureka Dashboard again – we can see them in the application list.
- Feign Client
Because we’re working with independent services, we need to make http calls from one to the other. One solution is RestTemplate, but there’s a better option. Netflix Feign Client is a rest client that can use the service discovery to call services. Spring Cloud makes it very easy to implement one. We just need to define an interface whose implementation will be provided at runtime. Start by adding the feign dependency to movie-service pom.
Then, enable the configuration in spring boot application class.
The hateoas dependency and `@EnableHypermediaSupport` are used to tell Feign how to deserialize the hypermedia data we receive from review-service. Now that we have all the configuration in place, we can move forward to the implementation.
All we need to do to create a feign client is define an interface. The @FeignClient annotation works like @RestController and the name parameter represents the unique name for the review-service. Then, we define the endpoints as we would in any rest controller – the only difference being that this is an interface.
To use this feign client, I created a rest controller to retrieve a movie by id with the reviews inside. We can see that we just inject the feign interface and use it like a jpa repository. The implementation will be provided to us at runtime.
Then, if we call the `/api/movies/{movieID}` endpoint, we should get this result:
- Hystrix Circuit BreakerÂ
Hystrix allows you to implement fallback logic for your feign clients. So, in case something breaks in the feign call – the request will fall back to default behavior. OpenFeign dependency and add hystrix to the classpath. You need to tell spring to use hystrix circuit breaker, using the `@EnableCircuitBreaker` on the spring application class.
There are two ways to define fallbacks. The difference between them is that the second one will give access to the underlying error that broke the circuit. To implement them, you need to define either a spring bean implement, the feign client interface or a bean that implements the `feign.hystrix.FallbackFactory` interface.
Option 1:
Option 2:
6. API Gateway
We’ve reached the stage where we implement the front door of our project. The gateway pattern allows you to define a single-entry point to the entire microservices architecture. The gateway will route all incoming requests from users to the specific backend service and can perform additional operations like validation or authentication.
For this implementation we will use Netflix Zuul. To do this, I created another application called ‘api-gateway’ that registers to eureka service discovery and has the spring cloud Netflix Zuul starter in the maven pom.
…And the configuration defined in spring boot application class.
All the routes that need to be redirected are defined in application properties – we need to make sure we use the service name as the route key and the path as its value.
So, in the example above, the request to the `/movie-service/movies` will be forwarded to `/movies` endpoint on `movie-service` service.
Using this pattern, we can isolate our services in a closed network, with no access from the exterior. Only the api-gateway can be exposed and can serve user requests, implement authentication, rate-limits, and other features – allowing backend services to focus on their function in a decoupled and independent fashion.
7. Conclusion: Building Microservices with Spring Boot is Like Building Any Other Application – with Some Added Sugar
Every architecture has its cost. There is no one-fits-all solution to its problems. You have to understand the business, plan for the future, and select the best architecture that suits those needs.
That’s not to say that microservices are always the go-to solution, but they might just be a great fit for complex, fast-growing applications. On the other hand, the monolith architecture can be a better fit for a lightweight application. In the end, every microservice is based on the monolith principles.
The code I used in this demo for building microservices with Spring Boot and Netflix OSS can be found on GitHub.