One of the most important steps in the lifecycle of an application is the design and architecture of the app. This is the most critical aspect prior to starting the implementation, deployment, continuous delivery, and maintenance of an application. The evolution in technologies and patterns always influences the design of applications as we seek to ensure performance and security.
What happens if the application stops working, or crashes for no reason or due to a lack of resources? How are you going to debug it if the error isn’t clear, or if the logs aren’t really good enough?
If you need to ask these questions, then you are on the wrong path – you are not working in a cloud-native application context.
The design of cloud-oriented applications has the objective of taking advantage of the benefits of the cloud. Even the software and services that manage these applications will be deployed in the cloud.
Cloud-native applications are typically microservices embedded in containers running on cloud computing infrastructure. Cloud-native applications use a microservice architecture. This architecture ensures the allocation of resources to each service used by the application more efficiently than the old approaches, such as monolithic applications. This makes an application flexible and adaptable to a cloud-oriented architecture.
Monoliths versus microservices
It is really important to understand the difference between the traditional monolithic approach and the microservices approach before defining the concept of microservices.
To scale a monolithic application, we have to clone the entire application on multiple servers or VMs. But for a microservices application, scaling is done by deploying every service in multiple instances across servers or VMs.
In a microservices approach, every application is composed of a collection of services that are related to specific functionalities. Every service can be developed, tested, deployed, versioned, and scaled. Monolithic applications are simple to use and easy to develop, deploy, and test, but they have limitations in size and complexity. Despite the simplicity of horizontal scaling, where we run multiple application copies behind a load balancer, it is difficult to do in the case of multiple modules that have conflicting resource requirements.
Microservices are very similar to beehives. Within a hive, thousands of bees coexist and help each other for a single common goal – the survival of the colony. The queen, the workers, and the drones – each one has their peculiarities and must, therefore, assume distinct tasks.
Monolithic and microservices architectures
Microservices are regularly discussed now in articles, on blogs, on social media, and even at conference presentations.
How do we use microservices to tackle complexity? In recent years, software architecture has evolved rapidly, from spaghetti-oriented architected where everything was a big jumble, to lasagna-oriented architecture where we can see the layers of architecture, to ravioli-oriented architecture where we talk about microservices. In this latter architecture, we split the application into sets of services instead of building a monolithic application. Maybe we will see pizza-oriented architecture next, where we use a serverless approach. Let’s now take a look at the layered architecture pattern and compare it to microservices.
The layered architecture pattern is the most common architecture pattern and is otherwise known as the n-tier architecture pattern. For distributed n-tier client/server applications, when taking a monolithic approach, you start with a hexagonal modular architecture, where you separate the domain model and the adapters (the devices used for inputs and outputs).
A monolithic application is composed of several layers, including different types of components or layers.
In this classic example, illustrated in Figure 1.2, we have four layers, from the user interface to the database where we store our data:
- Presentation layer: This presents the user interface layer; it can be a web or mobile or desktop application.
- Services layer: This is a set of standards, techniques, and methods. An application is split into services based on functionality and domain. This layer is responsible for handling HTTP requests and responding with either HTML or JSON/XML, as in the case of API services.
- Business logic layer: This holds the application’s business logic, the heart of our application. This entails custom business rules, such as operation definitions, constraints, and algorithms, that manage the exchange of information between the database layer and the presentation layer.
- Database access layer: This is an abstraction of the logical data model. The modification of the logical data model is done in the business layer, but we can perform even more complex data manipulations from multiple sources and send them back to the business layer. This layer will ensure access to the database.
Figure 1.2 – N-tier architecture pattern for monolithic and microservices architectures
The architecture presented here is logically modular, but the application is packaged and deployed in a single package as a monolith.
Let’s explore the different elements of the monolithic approach:
- Monolithic applications are easy to set up because they involve a single complete package. They include all the components, such as the GUI, business layer, data, and related necessary services encapsulated in a single package.
- This single package is developed in sequential order. At the start of each project, the designers work on the design of the product as well as the necessary documents to meet the needs of the client user. Then, the developers implement code and send it to the quality assurance department in order to carry out the necessary tests.
- The team of testers runs different types of tests, including integration tests, interface tests, and even performance tests, to identify errors and evaluate the performance of the cloud-native application.
- If they detect errors, code is sent back to the developers so that it can be debugged and corrected.
- Once the code passes all the tests, it is deployed in a test production environment similar to the final environment and then deployed in a real environment.
- If you want to modify the code, add a new feature, or even remove an old feature, you have to start the whole process again. If several teams are working on the same project, taking into account the changes in the teams as developers come and go, coordinating code changes is a challenge and will take a lot of time. Moreover, the deployment of a software project always requires a specific infrastructure configuration as well as the implementation of an extended functional test mechanism. Therefore, in a complex project with dynamic requirements, the whole process is inefficient and time-consuming. Microservices architecture can help to solve most of these challenges.
For microservices, the idea is very simple – we divide our application into a set of smaller interconnected services instead of creating a single monolithic application. This is an architectural design model based on the architectural principles of domain-driven design and DevOps – that is, if we have several domains in our application that can interact independently.
Each microservice represents a small application with its own hexagonal architecture. This application is composed of business logic and data.
Some microservices may implement a user interface, such as a web page or mobile application, while others may expose or consume a Representational State Transfer (REST), Application Programming Interface (API), or even a Remote Procedure Call (RPC). Services may be using a message-based system or simply consuming APIs provided by other services.
The services are independent, making it easy for developers to work independently on them without affecting the entire app. Developers also have the freedom to use different languages in different parts of the code simultaneously, via a central repository that acts as a version control system and updates specific features without disrupting the software or causing application downtime.
Developers can use a central container orchestrator to improve performance by managing automatic scheduling and the allocation of resources on demand, by using scaling features. Finally, microservices are adopted as enablers of DevOps and Continuous Integration (CI)/Continuous Delivery (CD), allowing them to be updated and deployed faster and without issues.
Microservices are deployed independently, each service having its own database, as shown in the previous diagram.
Microservices allow us to scale and deploy parts of an application independently. They offer great distributed software challenges, but with these benefits, microservices are not a universal solution for every app in the cloud, as they are intended mainly for large, scalable, and long-term distributed applications. Do not underestimate the complexity involved in implementation and testing.
We have discussed the application design and have explored the difference between monolithic and microservices approaches. Let’s now move on to application lifecycles.