Welcome to the exciting world of Microservices! In this chapter, we will try to understand what Microservices are, how and why we are shifting from age-old practice of creating a giant monolithic application to Microservices, and the advantages and challenges of using Microservices.
In this chapter,we will try to answer some of the important questions regarding Microservices, and then in the rest of the book, we will take a deeper look at things such as the creation of Microservices, security, communication, monitoring, and documentation.
In this chapter, we will cover the following topics:
- What is Monolith design?
- The challenges associated with Monolith design
- Service-oriented architecture
- Understanding Microservices
- The advantages of Microservices
- The challenges associated with Microservices
If you have been in the industry for more than six to eight years, monolithic applications are not new to you. Even today, a lot of old applications that are in production follow monolith design architecture. Well, let's try to understand what a monolith design is and what the word monolith means to you.
If you look at these definitions and try to apply them to a software application, it will be clear what we mean when we say the application follows a monolith design. We are talking about the following characteristics:
- Single: In the case of an application, we are talking about a single piece of code or a deployable. An application that can and should be deployed on a single machine.
- Indivisible: We cannot break the application down; there is no easy way to divide the application code or deployable.
- Slow to change: This is more of an implication of monolith design. It is a well-known fact that changing a small piece of code is easier than a big, monolith code, especially since you don't know what implications such a change will have.
We can see the whole application is deployed as a single deployable, that is, a WAR or EAR file. The design looks very simple, but it does hide a lot of complexities. The single deployable, in this case, a WAR file or EAR file, might have a lot of functionality implemented.
Let's take an example: say we have to implement an application for our company where we can manage data for employees. Let's call this the Employee Management System, which should be able to handle core requirements such as maintaining employee data, project data, hierarchy, attendance, leave data, salary, and financial information. In addition to these core requirements, our application would also handle additional requirements, such as reporting needs for management.
In a monolith design, we code all this functionality and build it into a single deployable. If you have been in the software industry for more than 10 years, you have probably seen and worked on some applications that follow this design. The design has been good for small and simple applications, but as the complexity of the application increases, it gets challenging to manage a monolith design.
- Huge code base: As we are developing the application as a single unit, everything is placed under a single code base. In our previous example, we have all the Employee-Management-related functionality in a single code base. So, even if we are making a small change, such as updating a tax slab for employee salaries, we have to take the whole code for our Employee Data Management project and build the whole application instead of just the tax-related part.
- Testing: As the application is managed as a single unit, we need to test the whole application, even if a small change is made, to make sure there are no integration or regression issues.
- Availability: Let's say that, while updating an employee data report, a developer introduced an error that caused the system to run out of memory, which will bring down the whole system. So a report that actually might not add too much value to the system and may be used rarely by users has the capability of bringing down the whole system.
- Scalability: The code is deployed as a single unit, hence we can only scale the application as a whole, making it a heavy operation. For example, if we just need to execute multiple instances of salary processing on pay day, we cannot do that in isolation; and we need to scale the whole application by providing more hardware firepower (vertical scaling) or make copies of the whole application on different machines (horizontal scaling).
- Inflexible: Say we need to create a special reporting feature and we know a tool or framework is available in a different language than we use. It is not possible in a monolith architecture to use different programming languages or technologies. We are stuck with the original choice, which might have been made years ago and is not relevant anymore.
- Difficult to upgrade: Even a small decision, such as moving from Java EE 6 to 8, would mean that the whole application, along with all the features, needs to be tested and even a small problem would hinder the upgrade, whereas if we were dealing with multiple small services, we could upgrade them independently and sequentially.
- Slow Development and time to market: For these reasons, it is difficult to make changes to an application with a monolith design. This approach does not fit well in today's agile world, where customers need to see changes and modifications as soon as possible, rather than waiting for a release that takes years.
We have talked about some of the challenges of monolith applications. As the application size grows and it becomes complex, it is not possible to manage a monolith application easily. Due to these challenges, the industry has explored different approaches to manage applications, with Microservices being a very popular solution.
Before moving to Microservices, it is important to understand what Service-oriented architecture (SOA) is. SOA is the base on which Microservices are built. As the name suggests, SOA is about services. With SOA, we try to visualize and design the application as a combination of services that can talk to each other and to the end user, and fulfill the user's requirements.
If we go back to our Employee Management System example, we can try to visualize the application as a set of different services:
- Employee Data Management
- Salary Management
- Project Data Management
- Attendance and Leave Management
- Reporting Management
Based on our needs, we can divide the application into various services.
The following diagram should help us to visualize our application:
Again, we do not need to get into complex decisions at this point, such as whether we should deploy these services as a single application or keep the data in a single database. Instead, we would like to emphasize the core idea in this section. The base of SOA is trying to think and visualize your application as a set of different services rather than a single, monolith deliverable.
Why does breaking the application into services help? A simple example is when we need to make changes to the code for salaries, the developer does not need to worry about what is happening in Employee-, project-, or attendance-related code. Also, if we were to get an error while generating a report for a manager, we would know we need to start looking for a problem in the reporting service. While testing, we will focus on only one aspect of the system. So the code becomes easy to manage and maintain.
Once we understand SOA and start to think of our application as a group of services, we can take the next step and move toward a Microservices-based architecture, which we'll discuss in the next section.
Microservices are not a completely new concept. In the past, there have been many attempts, such as EJBs,Remote procedure calls (RPC), and implementations of services through SOAP, that aimed to reduce dependencies among various application components. Let's look at a formal definition to set the context and then we will try to understand it in detail:
"Microservices - also known as the microservice architecture - is an architectural style that structures an application as a collection of loosely coupled services, which implement business capabilities. The microservice architecture enables the continuous delivery/deployment of large, complex applications. It also enables an organization to evolve its technology stack".
In the previous section, we discussed SOA. Microservices are the logical next step, where we extend the SOA and start thinking about dividing the services at a granular level. As the name suggests, we divide our services down to a Micro level when we are talking about Microservices. The next most important aspect is to think about these Microservices as independent entities that can be developed, tested, deployed, and managed as complete sub-applications in themselves.
If we try to visualize our previous example of the Employee Management System in terms of design, the following diagram should demonstrate a Microservices-based implementation:
Let's take a look at the design and how is it different from our previous SOA approach. First, notice that we have divided our services at a granular level. For example, we are supporting Excel and PDF-based reporting, but the actual generators need not be part of the reporting service. The reporting service should only be responsible for generating data, but how the data is represented should not be part of this service. An additional advantage it gives us is that, if in future, we want to support other formats, say a Word DOC report, we don't need to touch any of the existing code; we just create a new service, say Word Document report generator, and plug it in. So we can say that a Microservices-based architecture is easy to extend and maintain.
As we have divided the application into smaller services that can be managed independently, we also talk about smaller teams and decentralized management. Each team can take independent decisions on the design and technology stack they want to use, and hence there is no dependency on other teams. Each service can be easily tested independently.
Another thing you might have noticed is that each service is deployed independently. Well, this might not be the exact scenario and you might deploy multiple services on the same machine, but the idea is that you should have the capacity to deploy each service independently.
Though we have not looked at the data storage part, ideally, each service will manage its own data. There should be a single point of managing one data entity. This helps in the decentralization for data, which helps with easy scalability and maintenance.
Communication between the services is another important aspect you should consider when choosing a Microservices-based architecture. We need to decide whether the communication needs to be synchronous or asynchronous, through REST, Queue-based, or some other communication medium. A well-architected system will be fault-tolerant, as a failure in no single service can bring down the system as a whole, so there is no single point of failure.
There are no fixed, industry-wide set of rules to follow for a Microservices-based architecture. This causes a lot of confusion, as well as flexibility. But to keep it simple, let's take a look at some of the common characteristics of a good Microservices-based architecture:
- Decoupled architecture
- Independent deployables
- Decentralized data
- Smaller code bases and teams
- Decentralized management
Next, let's take a look at some of the advantages and challenges that can be expected when using a Microservices-based architecture.
Now that you're comfortable with the concept of Microservices, let's take a look at the advantages they provide, which has made them so popular over the last few years. We already discussed the challenges that come with a monolithic architecture. Most of the challenges of Monolithic applications can be handled by the use of a Microservices-based approach. The following are some of the advantages of a Microservices-based architecture:
- Easy-to-manage Code: As we are able to modularize and divide our huge application code base into various Microservices, we are not dealing with the whole application code at any point in time.
- Flexibility of choosing the tech stack: As every Microservice we create is potentially a separate application in itself with a different deployable, it is easy to choose a technology stack based on need. For example, if you have many Java-based services in an application, and a new requirement comes in which you feel can be easily handled by using Python instead of Java, you can go ahead build that service in Python, and deploy it without worrying about the rest of the application.
- Scalability: As every Microservice can be deployed independently, it is easy to scale them without worrying about the impact on others. For example, let's say we know the reporting service is used heavily at every end of a quarter – we can scale only this Microservice by adding more instances, and the rest of the services remain unaffected.
- Testing: Unit testing, to be more specific, is easy with a Microservices-based approach. If you are modifying the leave-management service with some new rules, you need not worry about other services, such as Employee Project Management. In the worst case, if your leave-management service breaks down due to faulty code, you can still edit and update the Employee project-related information without even knowing that some other service is broken.
- Faster time to market: As we are dealing with only one part of the application at a time, it is easier to make changes and move to production. Testing and deployment efforts are minimal as we are dealing with a subset of the whole system at a time.
- Easy to upgrade or modify: Let's say we need to upgrade a Microservice, upgrade software or hardware, or completely rewrite the service, this is much easier in a Microservice based architecture, as we are only upgrading one part of the application.
- Higher fault tolerance: In a monolith architecture, one error can cause the whole system to crash. In a Microservice-based architecture, in the worst case, a single service will crash, but no other services will be impacted. We still need to make sure we are managing errors properly to take advantage of Microservice-based architecture.
Before concluding the Microservices section, it's important to mention that Microservices are not a silver bullet. Along with all the advantages that come with Microservices, we need to be aware of the challenges that they bring if not used properly:
- The right level of modularization: You need to be very careful in determining how your application can be divided into Microservices. Too few would mean you're not getting the proper advantage of Microservices, and too many services means a heavy dev-ops requirement, to make sure all the Microservices work well when deployed together. Too many Microservices can also have a performance impact due to inter-service communication needs. You need to carefully analyze the application and break it down into logical entities, based on what would make sense to be thought of as a separate module.
- Different tech stacks to manage: One of the advantages of a Microservices-based architecture is that you are not dependent on one technical stack or language. For example, if one of the services is coded in Java, you can easily build another one in .NET or Python. But if you are not careful, this advantage can quickly become a problem. You might end up supporting dozens of technical stacks and managing expertise for each service independently. Movementof team members between projects or among teams is not an option if required, as one team might be working on a completely different tech stack than other.
- Heavy reliance on Dev-Ops: If you are using too many Microservices, you need to monitor each one and make sure all the communication channels are healthy at all times.
- Difficult fault management: If you have dozens of services communicating with each other, and one of those goes down or is slow to respond, it becomes difficult to identify the problem area. Also, you do not want that problem in a single service to impact other services, so you will need to make sure arrangements are in place to handle error situations.
- Managing the data: As a rule of thumb, we try to make sure every Microservice manages its own data. But this is not always easy when data is shared among services, so we need to determine how much data each service should own and how the data should be shared among services.
When you are architecting the system, you need to make sure you understand these challenges and take care of them before committing to a solution. We will discuss some of these challenges in this book and approaches to handling them.
In this chapter, we discussed the basics of Microservices. We started by discussing Monolith architecture, and challenges that make it unfit for larger applications. After that, we discussed the SOA, which can safely be considered as the basis of Microservices. Finally, we talked about a Microservices-based architecture, as well as its characteristics, advantages, and challenges.
Throughout the rest of the book, we will look at different aspects of Microservices; we will talk about development, security, monitoring, and other aspects that you will need to be aware of when you are implementing Microservices. In the next chapter, we will discuss developing your first Microservice.