Book Image

Spring 5.0 Microservices - Second Edition

By : Rajesh R V
Book Image

Spring 5.0 Microservices - Second Edition

By: Rajesh R V

Overview of this book

The Spring Framework is an application framework and inversion of the control container for the Java platform. The framework’s core features can be used by any Java application, but there are extensions to build web applications on top of the Java EE platform. This book will help you implement the microservice architecture in Spring Framework, Spring Boot, and Spring Cloud. Written to the latest specifications of Spring that focuses on Reactive Programming, you’ll be able to build modern, internet-scale Java applications in no time. The book starts off with guidelines to implement responsive microservices at scale. Next, you will understand how Spring Boot is used to deploy serverless autonomous services by removing the need to have a heavyweight application server. Later, you’ll learn how to go further by deploying your microservices to Docker and managing them with Mesos. By the end of the book, you will have gained more clarity on the implementation of microservices using Spring Framework and will be able to use them in internet-scale deployments through real-world examples.
Table of Contents (11 chapters)

Microservices benefits


Microservice offers a number of benefits over the traditional multi-tier monolithic architectures. This section explains some of the key benefits of the microservices architecture approach.

Supports polyglot architecture

With microservices, architects and developers get flexibility in choosing the most suitable technology and architecture for a given scenario. This gives the flexibility to design better fit solutions in a more cost-effective way.

Since microservices are autonomous and independent, each service can run with its own architecture or technology, or different versions of technologies.

The following image shows a simple, practical example of polyglot architecture with microservices:

There is a requirement to audit all system transactions and record transaction details such as request and response data, users who initiated the transaction, the service invoked, and so on.

As shown in the preceding diagram, while core services like Order microservice and Product microservice use a relational data store, the Audit microservice persists data in a Hadoop File System (HDFS). A relational data store is neither ideal nor cost effective to store large data volumes, like in the case of audit data. In the monolithic approach, the application generally uses a shared, single database that stores the Order, Product, and Audit data.

In this example, audit service is a technical microservice using a different architecture. Similarly, different functional services could also use different architectures.

In another example, there could be a Reservation microservice running on Java 7, while a Search microservice could be running on Java 8. Similarly, an Order microservice could be written on Erlang, whereas a Delivery microservice could be on the Go language. None of these are possible with a monolithic architecture.

Enables experimentation and innovation

Modern enterprises are thriving toward quick wins. Microservices is one of the key enablers for enterprises to do disruptive innovation by offering the ability to experiment and Fail Fast.

Since services are fairly simple and smaller in size, enterprises can afford to experiment with new processes, algorithms, business logic, and more. With large monolithic applications, experimentation was not easy, straightforward, or cost effective. Businesses had to spend a large sum of money to build or change an application to try out something new. With microservices, it is possible to write a small microservice to achieve the targeted functionality, and plug it into the system in a reactive style. One can then experiment with the new function for a few months. Moreover, if the new microservice is not working as expected, change or replace it with another one. The cost of change will be considerably less compared to the monolithic approach:

In another example of an airline booking website, the airline wants to show personalized hotel recommendations in their booking page. The recommendations have to be displayed on the booking confirmation page.

As shown in the preceding diagram, it is convenient to write a microservice that can be plugged into the monolithic applications booking flow rather than incorporating this requirement in the monolithic application itself. The airline may choose to start with a simple recommendation service, and keep replacing it with newer versions until it meets the required accuracy.

Elastically and selectively scalable

Since microservices are smaller units of work, it enables us to implement selective scalability and other Quality of Services (QoS).

Scalability requirements may be different for different functions in an application. Monolithic applications are generally packaged as a single war or an ear. As a result, applications can only be scaled as a whole. There is no option to scale a module or a subsystem level. An I/O intensive function, when streamed with high velocity data, could easily bring down the service levels of the entire application.

In the case of microservices, each service could be independently scaled up or down. Since scalability can be selectively applied for each service, the cost of scaling is comparatively less with the microservices approach.

In practice, there are many different ways available to scale an application, and this is largely constraint to the architecture and behavior of the application. The Scale Cube defines primarily three approaches to scale an application:

  • X-axis scaling, by horizontally cloning the application
  • Y-axis scaling, by splitting different functionality
  • Z-axis scaling, by partitioning or sharding the data.

Note

Read more about Scale Cube at http://theartofscalability.com/.

When Y-axis scaling is applied to monolithic applications, it breaks the monolithic into smaller units aligned with business functions. Many organizations successfully applied this technique to move away from monolithic application. In principle, the resulting units of functions are inline with the microservices characteristics.

For instance, on a typical airline website, statistics indicates that the ratio of flight search versus flight booking could be as high as 500:1. This means one booking transaction for every 500 search transactions. In this scenario, search needs 500 times more scalability than the booking function. This is an ideal use case for selective scaling:

The solution is to treat search requests and booking requests differently. With a monolithic architecture, this is only possible with Z scaling in the scale cube. However, this approach is expensive, as, in Z scale, the entire codebase will be replicated.

In the preceding diagram, Search and b are designed as different microservices so that Search can be scaled differently from Booking. In the diagram, Search has three instances and Booking has two instances. Selective scalability is not limited to the number of instances, as shown in the preceding diagram, but also in the way in which the microservices are architected. In the case of Search, an In-Memory Data Grid (IMDG) such as Hazelcast can be used as the data store. This will further increase the performance and scalability of Search. When a new Search microservice instance is instantiated, an additional IMDG node will be added to the IMDG cluster. Booking does not require the same level of scalability. In the case of Booking, both instances of the Booking microservices are connected to the same instance of the database.

Allows substitution

Microservices are self-contained independent deployment modules, enabling us to substitute one microservice with another similar microservice.

Many large enterprises follow buy-versus-build policies for implementing software systems. A common scenario is to build most of the functions in-house and buy certain niche capabilities from specialists outside. This poses challenges in the traditional monolithic applications since these application components are highly cohesive. Attempting to plug in third-party solutions to the monolithic applications results in complex integrations. With microservices, this is not an afterthought. Architecturally, a microservice can be easily replaced by another microservice developed, either in-house or even extended by a microservice from a third party:

A pricing engine in the airline business is complex. Fares for different routes are calculated using complex mathematical formulas known as pricing logic. Airlines may choose to buy a pricing engine from the market instead of building the product in-house. In the monolithic architecture, Pricing is a function of Fares and Booking. In most cases, Pricing, Fares, and Bookings are hardwired, making it almost impossible to detach.

In a well-designed microservices system, Booking, Fares, and Pricing will be independent microservices. Replacing the Pricing microservice will have only a minimal impact on any other services, as they are all loosely coupled and independent. Today, it could be a third-party service, tomorrow, it could be easily substituted by another third-party service or another home grown service.

Enables to build organic systems

Microservices help us build systems that are organic in nature. This is significantly important when migrating monolithic systems gradually to microservices.

Organic systems are systems that grow laterally over a period of time by adding more and more functions to it. In practice, applications grow unimaginably large in its lifespan, and, in most cases, the manageability of the application reduces dramatically over that same period of time.

Microservices are all about independently manageable services. This enables us to keep adding more and more services as the need arises, with minimal impact on the existing services. Building such systems do not need huge capital investment. Hence, businesses can keep building as part of their operational expenditure.

A loyalty system in an airline was built years ago, targeting individual passengers. Everything was fine until the airline started offering loyalty benefits to their corporate customers. Corporate customers are individuals grouped under corporations. Since the current system's core data model is flat, targeting individuals, the corporate requirement needs a fundamental change in the core data model, and hence, a huge rework to incorporate this requirement.

As shown in the following diagram, in a microservices-based architecture, customer information would be managed by the Customer microservices, and loyalty by the Loyalty microservice:

In this situation, it is easy to add a new Corporate Customer microservice to manage corporate customers. When a corporation is registered, individual members will be pushed to the Customer microservices to manage them as usual. The Corporate Customer microservice provides a corporate view by aggregating data from the Customer microservice. It will also provide services to support corporate-specific business rules. With this approach, adding new services will have only a minimal impact on existing services.

Helps managing technology debt

Since microservices are smaller in size and have minimal dependencies, they allow the migration of services that are using end-of-life technologies with minimal cost.

Technology changes are one of the barriers in software development. In many traditional monolithic applications, due to the fast changes in technology, today's next generation applications could easily become legacy, even before releasing to production. Architects and developers tend to add a lot of protection against technology changes by adding layers of abstractions. However, in reality, this approach doesn't solve the issue, but, instead, it results in over-engineered systems. Since technology upgrades are often risky and expensive, with no direct returns for the business, the business may not be happy to invest in reducing the technology debt of the applications.

With microservices, it is possible to change or upgrade technology for each service individually, rather than upgrading an entire application.

Upgrading an application with, for instance, five million lines written on EJB 1.1 and Hibernate to Spring, JPA, and REST services is almost like rewriting the entire application. In the microservices world, this could be done incrementally.

As shown in the preceding diagram, while older versions of the services are running on old versions of technologies, new service developments can leverage the latest technologies. The cost of migrating microservices with end-of-life technologies will be considerably less compared to enhancing monolithic applications.

Allowing co-existence of different versions

Since microservices package the service runtime environment along with the service itself, it enables multiple versions of the service to coexist in the same environment.

There will be situations where we will have to run multiple versions of the same service at the same time. Zero downtime promote, where one has to gracefully switch over from one version to another, is one example of such a scenario, as there will be a time window where both services will have to be up and running simultaneously. With monolithic applications, this is a complex procedure, since upgrading new services in one node of the cluster is cumbersome as, for instance, this could lead to class loading issues. A Canary release, where a new version is only released to a few users to validate the new service, is another example where multiple versions of the services have to coexist.

With microservices, both these scenarios are easily manageable. Since each microservice uses independent environments, including the service listeners such as embedded Tomcat or Jetty, multiple versions can be released and gracefully transitioned without many issues. Consumers, when looking up services, look for specific versions of services. For example, in a canary release, a new user interface is released to user A. When user A sends a request to the microservice, it looks up the canary release version, whereas all other users will continue to look up the last production version.

Care needs to be taken at database level to ensure that the database design is always backward compatible to avoid breaking changes.

As shown in the following diagram, version V01 and V02 of the Customer service can coexist as they are not interfering with each other, given their respective deployment environment:

Routing rules can be set at the gateway to divert traffic to specific instances, as shown in the diagram. Alternatively, clients can request specific versions as a part of the request itself. In the diagram, the gateway selects the version based on the region from which the request originated.

Supporting building self-organizing systems

Microservices help us build self-organizing systems. A self-organizing system supporting automated deployment will be resilient and exhibits self-healing and self-learning capabilities.

In a well-architected microservice system, services are unaware of other services. It accepts a message from a selected queue and processes the message. At the end of the process, it may send out another message that triggers other services. This allows us to drop any service into the ecosystem without analyzing the impact on the overall system. Based on the input and output, the service will self-organize into the ecosystem. No additional code changes or service orchestration is required. There is no central brain to control and coordinate the processes.

Imagine an existing notification service that listens to an INPUT queue and sends notifications to a Simple Mail Transfer Protocol (SMTP) server as follows:

If later, a personalization engine needs to be introduced to personalize messages before sending it to the customer, the personalization engine is responsible for changing the language of the message to the customer's native language.

The updated service linkage is shown as follows:

With microservices, a new personalization service will be created to do this job. The input queue will be configured as INPUT in an external configuration server. The personalization service will pick up the messages from the INPUT queue (earlier, this was used by the notification service), and send the messages to the OUTPUT queue after completing the process. The notification service's input queue will then send to OUTPUT. From the very next moment onward, the system automatically adopts this new message flow.

Supporting event-driven architecture

Microservices enable us to develop transparent software systems. Traditional systems communicate with each other through native protocols and hence behave like a black-box application. Business events and system events, unless published explicitly, are hard to understand and analyze. Modern applications require data for business analysis, to understand dynamic system behaviors, and analyze market trends, and they also need to respond to real-time events. Events are useful mechanisms for data extraction.

A well-architected microservice always works with events for both input and output. These events can be tapped by any services. Once extracted, events can be used for a variety of use cases.

For example, businesses want to see the velocity of orders categorized by the product type in real-time. In a monolithic system, we will need to think about how to extract these events, which may impose changes in the system.

The following diagram shows the addition of New Event Aggregation Service without impacting existing services:

In the microservices world, Order Event is already published whenever an order is created. This means that it is just a matter of adding a new service to subscribe to the same topic, extract the event, perform the requested aggregations, and push another event for the dashboard to consume.

Enables DevOps

Microservices are one of the key enablers of DevOps. DevOps is widely adopted as a practice in many enterprises, primarily to increase the speed of delivery and agility. Successful adoption of DevOps requires cultural changes and process changes, as well as architectural changes. It advocates to have agile development, high velocity release cycles, automatic testing, automatic infrastructure provisioning, and automated deployment. Automating all these processes is extremely hard to achieve with traditional monolithic applications. Microservices are not the ultimate answer, but microservices are at the center stage in many DevOps implementations. Many DevOps tools and techniques are also evolving around the use of microservices.

Considering a monolithic application takes hours to complete a full build and twenty to thirty minutes to start the application, one can see that this kind of application is not ideal for DevOps automation. It is hard to automate continuous integration on every commit. Since large monolithic applications are not automation friendly, continuous testing and deployments are also hard to achieve.

On the other hand, small footprint microservices are more automation-friendly, and, therefore, they can more easily support these requirements.

Microservices also enables smaller, focused agile teams for development. Teams will be organized based on the boundaries of microservices.