In this blog, I will share my thoughts on how to architect complex software using microservices architecture, so that it’s flexible, scalable, and a competitive piece of software. I’ll start off first by introducing microservices, and what was before microservices, why microsoervices are so successful and useful now, and the design principles that are associated with microservices architecture.
What Is a Service?
A service is a piece of software which basically provides functionality to other components of software within your system. It basically provides a service to other pieces of software. The other pieces of software could be anything from a website to a mobile app or a desktop app, or even another service. The service basically provides functionality to these applications. And the communication between the software components and the service normally happen using some kind of communication protocol. A system which uses a service or multiple services in this fashion is known to have a service-oriented architecture, and this is normally abbreviated as SOA, or SOA, and the main idea behind SOA is, instead of building all the functionality in one big application, I instead use a service to provide a subset or just one functionality to the application, and this allows me to have many applications use the same cod, and in the future I can have newer or different types of systems connecting to the same service, reusing that functionality, and as a software architecture, SOA has been successful. It allows us to scale and to reuse functionality. A key characteristic of service-oriented architecture is the idea of having standardized contracts or interfaces. When a client application called the service, it called the service by calling a method. The signature of that method normally doesn’t change when the service changes, so you can upgrade a service without having to upgrade the clients, as long as the contract and the interface, i.e., the signature of the method doesn’t change. Also a service is stateless, so when a request comes in from a website to our service, that instance of the service does not have to remember the previous request from that specific customer, that specific client, it basically has all the information from the request that it needs in order to retrieve all the data associated with previous requests within the service. The microservices architecture is basically an improved version of service-oriented architecture, or in other terms SOA done the right way. Microsoervices Architecture shares all the key characteristics of the service-oriented architecture, of scalability, reusability, and standardized contracts and interface for backwards compatibility, and the idea of having a service that’s stateless.
The microservices architecture is basically service-oriented architecture done well. Microservices basically introduce a new set of additional design principles to size a service correctly. Because there was no guidance in the past on how to size a service and what to include in a service, traditional service-oriented architecture resulted in monolithic large services, and because of the size of the service, these services became inefficient to scale up and change in an allowable way. Smaller services, i.e., microservices, basically provide services which are more efficiently scalable, which are flexible, and which can provide high performance in the areas where performance is required. An application which is based on microservices architecture is normally an application which is powered by multiple microservices, and each one of these microservices will provide a set of functions, a set or related functions, to a specific part of the application. Because the microservice normally has a single focus, it does one thing and it does it well. Microservice architecture also uses lightweight communication mechanism between clients and services and service to service. The communication mechanism has to be lightweight and quick, because when you carry out a transaction within a microservices architectured system, the transaction will be a distributed transaction which is completed by multiple services, therefore the services need to communicate to each other in a quick and efficient way over the network. The application interface for a microservice, also needs to be technology agnostic. This basically means the service needs to use an open communication protocol so that it does not dictate the technology that the client application needs to use. And by using open communication protocols, for example, like HTTP REST, we could easily have a .NET client application which talks to a Java-based microservice. In a monolithic service, you’re also likely to have a central database in order to share data between applications and services. In microservices architecture, each microservice has its own data storage. Another key characteristic of a microservice is that it is independently changeable. I can upgrade, enhance or fix a specific microservice without changing any of the clients or any of the other services within the system. And because microservices are independently changeable, they also need to be independently deployable. By modifying one microservice, I should be able to then deploy that change within my system independently from everything else without deploying anything else. We’ve already mentioned the fact that when you make a transaction within a microservices architectured system, the transaction is most likely to be completed by multiple services, multiple services which are distributed, and therefore, your transaction is also a distributed transaction. And because a microservices architectured system has so many moving parts, there’s a need for centralized tooling for management of the microservices. You need a tool, which will help you manage and see the health of your system because there are so many moving parts.
One of the reasons for the microservices architecture now is the need to respond to change quickly. The software market is really competitive nowadays. If your product can’t provide a feature that’s in demand, it will lose market share very quickly, and this is where microservices can split a large system into parts so we can upgrade and enhance individual parts in line with the market needs. So not only do we need to change parts of our system quickly, we also need to change them in a reliable way in order to keep the market happy, and microservices provides this reliability by having your system in many parts, so if one part of the system breaks it won’t break the entire system.
There is also a need for business domain-driven design. The architecture of our application needs to match the organization structure, or the structure of the business functions within the organization. Another reason why microservices architecture is now possible is because we now have automated test tools. We’ve already seen that in a microservices architecture transactions are distributed, and therefore, a transaction will be processed by several services before it’s complete. Therefore, the integration between those services needs to be tested, and testing these microservices together manually might be quite a complex task, but the good news is these automated tests automatically test the integration between our microservices, and this is why microservices architecture is now possible, because we have automated test tools which test integration between services. Release and deployment of microservices can also be complex, but we also have tools, centralized tools, which can carry out this function.
Another reason for the need to adopt microservices architecture is the need to adopt new technology. Because our system is now in several moving parts, we can easily change one part, i.e., a microservice from one technology stack to another technology stack in order to get a competitive edge. Another advancement in technology which makes microservices possible is the asynchronous communication technology. In our microservices architecture when we use distributed transactions, the distributed transaction might use several services in order to complete. Using asynchronous communication, the distributed transaction does not have to wait for individual services to complete their tasks before it’s complete.
The key benefits of the microservices Architecture
- Microservices have shorter development times. Because the system is split up into smaller moving parts, you can work on a moving part individually, you can have teams working on different parts concurrently, and because microservices are small in size and they have a single focus, the team have less to worry about in terms of scope. They know the one thing they’re working on has a certain scope, and there’s no need to worry about the entire system as long as they honor the contracts between the services.
- More reliable and faster Deployment. Because theservices are loosely coupled, developers can rework, change, and deploy individual components without deploying or affecting the entire system, and therefore, deployment is more reliable and faster. Shorter development times and reliable and faster deployment also enable frequent updates. As we’ve already briefly mentioned, frequent updates can give you a competitive edge in the marketplace. The microservices architecture also allows us to decouple changeable parts. For example, if we know our UI for our system, our user interface for our system, changes quite often, if it uses the microservices architecture, the UI is most likely decoupled from all the services in the background, and therefore you can change it independently from all the services.
- Enhanced Security. Microservices architecture also increases security. In a monolithic system you might have one central database with one system accessing that database, and therefore, all you need to do is hack that one system in order to gain access to the data. In the microservices architecture, each microservice has its own database, and each microservice can also have its own security mechanism, therefore, making the data distributed and making the data even more secure.
- Increased uptime, because when it comes to upgrading the system you will probably deploy one microservice at a time without affecting the rest of the system. And because the system is split up into business domains and business functions, when a problem arises we can probably quickly identify which service is responsible for that specific business function, and therefore resolve the problem within that microservice.
- Highly scalable, and better performance. When there’s a specific part of the system which is in demand, we can just scale that specific part up, instead of scaling the whole system up.
- Better support for distributed teams. We can give the ownership of a microservice to a particular development team so that there’s better ownership and knowledge about the microservice. Microservices allow us to use the right technology for specific parts in the system, and because each microservice is separate from the other microservice, they don’t share databases, and they have their own code base, you can easily have microservices being worked on concurrently by distributed teams. In the section of the module we’ll start looking at the design principles that enable microservices and enable these benefits that we get from microservices.
Microservices Design Principles
Basically, the microservices content and functionality in terms of input and output must be coherent. It basically must have a single focus, and the thing it does it should do well within that single focus. The idea of a microservice having a single focus or a single responsibility is actually taken from the SOLID coding principles, and the single responsibility principle basically states that a class can only change for one reason, and this same principle is applied to microservices. It’s a useful principle because it allows us to control the size of the service, and we will not accidentally create a monolithic service by attaching other behaviors into the microservice which are not actually related. Because the high cohesion principle controls the size of the microservice and the scope of the contents of the microservice, the microservice is easily rewriteable as we are likely to have less of an attachment to a smaller code base, and obviously there will be fewer lines of code to rewrite because the microservice will be so small. And overall, if all our microservices have high cohesion, it makes our overall system highly scalable, flexible, and reliable. The system is more scalable because we can scale up individual microservices, which represent a specific business function or business domain which is in demand, instead of scaling up the whole system, and at the same time the system is more flexible because we can change and upgrade or change the functionality of specific business functions or business domains within our system, and then we have reliability because overall we are changing specific small parts within the system without affecting other parts within the system.
Microservices should also be autonomous. Autonomous meana a microservice should not be subject to change because of an external system it interacts with or an external system that interacts with it. There should be loose coupling between the microservices and between the microservices and the clients that use the microservices. A microservice should also be stateless. There should be no need to remember previous interactions that clients might have had with this service or other service instances in order to carry out the current request. And because microservices honor contracts and interfaces to other services and clients, they should be independently changeable and independently deployable. They should just slot back into the system after a change or enhancement, even though it has a newer version than any of the other components within the system. This also ensures our service is always backwards compatible. Having clear defined contracts between services also means that microservices can be concurrently developed by several teams, because there’s a clear definition of the input and output of a microservice. Separate teams can work on separate microservices, as long as they honor the contracts, development should go okay.
Business Domain Centric
A microservice should also be a business domain centric, and this means a service should represent a business function. The overall idea is to have a microservice represent a business function or a business domain, i.e., a part of the organization, because this helps scope the service and control the size of the service. This is an idea which is taken from domain driven design. You basically define a bounded context, which basically contains all the functionality which is related to a specific part of the business to a business domain or a business function, and you define the bounded context by defining boundaries and seams within the code. You basically highlight the areas where related functionality exists. There will be times when code relates to two different bounded contexts, and this is where we need to shuffle the code around so that the code ends up in the right place where it makes sense and it belongs in terms of business function or business domain. We need to aim for high cohesion, remember, making our microservices business domain centric, or to make our microservices responsive to business change. So as the business changes or the organization changes or functions within the business change, our microservices can change in the same way because our system is broken up into individual parts which are business domain centric. We can also change those parts which relate to specific parts in the organization which are changing.
Microservices are resiliant. They embrace failure when it happens. Failure might be in the form of another service not responding to your service, or it might be a connection line to another system which has gone down, or it might be a third party system which fails to respond. Whatever the type of failure, our microservice needs to embrace that failure by degrading the functionality within our microservice or by using default functionality. An example of degrading functionality might be a scenario where we have a user interface microservice which basically draws an HTML page for available orders and promotions, but for whatever reason the promotion’s microservice is down and fails to respond, so our user interface microservice basically chooses to degrade that functionality, and it chooses not to display the promotions on the page. Another way of making microservices more resilient is by having multiple instances of microservices so they register themselves as they start up, and if any of them fail they deregister themselves, so our system or our load balancers, etc., are only ever aware of fully functioning microservices. We also need to be aware that there are different types of failures. So, for example, there might be exceptions or errors within a microservice, there might be delays in replying to a request, and there might also be complete unavailability of a microservice, and this is where we need to work out if we did need to degrade functionality or if we need to default functionality. Failures are also not just limited to the software itself. You might have network failures, and remember we’re using distributed transactions here where one transaction might go across the network and use several services before it actually completes, and therefore, again, we need to make our microservices resilient to network delays or unavailability. We also need to ensure that when our microservices are called and the input they receive as part of that request, that we can validate that input, and this might be input from services or from clients. We need to ensure that our microservices are resilient and can validate incoming data, and they don’t basically fall over because they’ve received something in an incorrect format.
We need a way to be able to observe our system’s health in terms of system status, in terms of logs, i.e., activity currently happening in the system and errors that are currently happening in the system. And this type of monitoring and logging needs to be centralized so that there is one place where we need to go to in order to view this information regarding the system’s health, and we need this level of monitoring and logging in a centralized place because we now have distributed transactions. In order for a transaction to complete, it must go across the network and use several services, therefore, knowing the health of the system is vital, and this kind of data will also be useful for quick problem solving because the whole system is distributed and there’s a lot going on. We need a quick way of working out where a potential problem possibly lies.
Automation in the form of tools, for example, tools to reduce testing. Automated testing will reduce the amount of time required for manual regression testing, and the time taken to test integration between services and clients, and also the time taken to set up test environments. Remember, in a microservices architecture our system is made up of several moving parts, and therefore testing can be quite complex, and this is where we need testing tools to automate some of that testing. We need tools, automated testing tools which give us quick feedback, so as soon as I change the microservice and check that we have called into our control system. As well as automation tools to help with testing, we need automation tools to help with deployment, a tool which basically provides a pipeline to deployment. It gives our microservice a deployment ready status, so when you check a change in, the tests pass, and then the deployment status is at ready, and then the tool knows that this build of the microservice is now ready for deployment. So not only does this tool provide a pipeline with a status for each deployable build of a microservice, it also provides a way of actually physically moving the build to the target machine or the target cloud system, so the physical deployment of the software will be all automatic, and therefore it will be reliable because it’s preconfigured with a target where the software needs to go, and it will be configured and tested once, and therefore it should work every time. The idea of using automation tools for deployment falls under a category called continuous deployment.