Over the last decade, the Spring Framework has evolved into the most popular framework for developing Java business applications. The Spring Framework has facilitated the development of loosely coupled applications that can be tested. However, the world has significantly changed in comparison to a decade ago. In the meantime, applications have grown into monolithic ones, making them difficult to maintain. Because of this, new architectures began to develop.
What is Microservice?
Microservice is an architectural style that focuses on building small, independently applicable, capability-based services.
Some of the most popular definitions are as follows:
“Microservices are small, autonomous services that work together” (Sam Newman, Thoughtworks)
“Loosely coupled service-oriented architecture with bounded contexts” (Adrian Cockcroft, Battery Ventures)
“A microservice is an independently deployable component of bounded scope that supports interoperability through message-based communication. Microservice architecture is a style of engineering highly automated, evolvable software systems made up of capability-aligned microservices"(Microservice Architecture, Irakli Nadareishvili, Ronnie Mitra, Matt McLarty)
The Microservice Architecture
Monolithic applications have a single deployable unit. The following figure shows an example of a monolithic application with three modules; Module 1, 2, and 3. Each of these modules is usually a business capability that is part of the monolithic application. For instance, in a shopping application, one of the modules could be a product recommendation.
The following figure shows what this monolithic application would look like when developed using the microservice architecture:
Modules are identified based on business capabilities
Each module is independently deployable. In the following example, Modules 1, 2, and 3 are separate deployable units. If there is a change in the business functionality of Module 3, we can individually build and deploy Module 3.
High Code Quality
Small and Lightweight
Single Business Capability
Automated Build and Release
There are new languages, frameworks, and automation capabilities emerging every day. It is important that the application architecture allows for flexibility in order to adapt to new capabilities. The microservice architecture involves creating small services. Within limitations, most organizations provide individual teams with new technology. This allows the teams to experiment with new technologies and innovate faster. In turn, this helps applications adapt and stay up to date with the evolution of technology.
The load on different parts of the application is very different. For example, in the case of a flight booking application, the client typically searches for flights multiple times before deciding whether to make a booking. The load in the search module is several times higher than the load in the reservation module. The microservice architecture provides for the flexibility to configure multiple instances of the search service with multiple instances of the reservation service. The following figure shows how we can increase specific microservices based on load:
According to O'Reilly; microservice architecture has significant advantages. However, there are significant challenges too. Deciding the boundaries of microservices is a challenging but important decision. Since microservices are small, and there are hundreds of microservices in a large enterprise, having great automation and visibility is critical.
With the microservice architecture, you are splitting up a large application into multiple microservices, so the number of builds, releases, and deployments increases multifold. It would be very inefficient to have manual processes for these steps. Test automation is critical to enable faster time-to-market. Teams should be focused on identifying automation possibilities as they emerge.
The cloud is disrupting the world. Several possibilities have emerged that were never present before. Organizations are able to provision computing, network, and storage devices on demand. This has a high potential for reducing costs in a number of industries. Consider the retail industry, where there are pockets of high demand (Black Friday, the holiday season, and so on).
Why should they pay for hardware throughout the year when they could provision it on demand?
While we would like to be benefit from the possibilities of the cloud, these possibilities are limited by architecture and by the nature of applications.
How do we build applications that can be easily deployed on the cloud? That's where cloud-native applications come into the picture.
Cloud-native applications are those that can easily be deployed on the cloud. These applications share a few common characteristics. We will begin by looking at the Twelve-Factor App – a combination of common patterns among cloud-native applications. The Twelve-Factor App evolved from the experiences of engineers at Heroku. It is a list of patterns that are used in cloud-native application architectures. It is important to note that an app here refers to a single deployable unit. Essentially, every microservice is an app (because each microservice is independently deployable).
Maintain One Code Base
Each app has one code base in revision control. There can be multiple environments where the app can be deployed. However, all these environments use code from a single codebase. An example antipattern is building a deployable environment from multiple codebases.
All dependencies must be explicitly declared and isolated. Typical Java applications use build management tools such as Maven and Gradle to isolate and track dependencies.
The app should store configuration in the environment. While environment variables are recommended in order to manage configuration in a Twelve-Factor App, other alternatives, such as having a centralized repository for application configuration, should be considered for more complex systems.
Applications depend on other services being available – data stores and external services, among others. The Twelve-Factor App treats backing services as attached resources. A backing service is typically declared via an external configuration.
Build, Release, Run
The build, release, and run phases are described as follows. We should maintain a clear separation between all three phases:
- Build: Creates an executable bundle (EAR, WAR, or JAR) from code, as well as dependencies that can be deployed to multiple environments
- Release: Combines the executable bundle with a specific environment configuration to deploy in an environment
- Run: Runs the app in an execution environment using a specific release
A Twelve-Factor App does not have a state. All the data that it needs is stored in a persistent store.
A Twelve-Factor App exposes all services using port binding. While it is possible to have other mechanisms to expose services, these mechanisms are implementation. Port binding gives full control of receiving and handling messages irrespective of where an app is deployed.
A Twelve-Factor App is able to achieve more concurrency by scaling out horizontally. Scaling vertically has its limits. Scaling out horizontally provides opportunities to expand without limits.
A Twelve-Factor App should promote elastic scaling. Hence, it should be disposable. It can be started and stopped when needed.
All the environments (development, test, staging, and production) should be similar. They should use the same processes and tools. With continuous deployment, they should very frequently have similar code. This makes finding and fixing problems easier.
Logs as Event Streams
Visibility is critical to a Twelve-Factor App. Since applications are deployed on the cloud and automatically scaled, it is important that you have a centralized view of what's happening across different instances of the applications.
No Distinction of Admin Processes
Twelve-Factor Apps treat administrative tasks (migrations, scripts) and normal application processes similarly.
The ideas and concepts of cloud-native computing introduced a new way to implement complex, scalable systems. Even if you’re not hosting your application on a cloud platform, these new ideas will influence how you develop applications in the future. Microservices provide a new way to structure your system. They introduce new challenges, but they also shift the attention to the design of each component. That improves encapsulation and allows you to implement maintainable components that you can quickly adapt to new requirements. For more details, I highly recommend the book Mastering Spring 5.