Build highly available, scalable, and performant applications by following this complete guide to building microservices in Azure, AWS, and Google Cloud.
In 2005, Dr. Peter Rodgers addressed micro web services during a presentation at a Web Services Edge conference, where the first generation of micro web services was based on service-oriented architecture (SOA). SOA is a ΓÇ£self-contained software module that performs as a task, and this allows the service to communicate based on SOAP (Simple Object Access Protocol). The main SOAP idea is, do the simplest thing possible.
Nowadays, there is no option to avoid talking about microservices during architectural calls, especially if you want to design cloud or multi-cloud, modular, scalable, and multi-user applications. In this article, I will explain microservices and how to design applications based on the multi-cloud scenario. I will walk you through the microservice design pattern and wrap this information into an architectural example.
This is an article from DZone’s 2022 Microservices and Containerization Trend Report. For more: Read the Report
Microservices and Multi-Cloud Environments
Before we jump into microservices architecture, patterns, and how you can build microservices-based applications that support multi-cloud deployment, let’s define the terms. A microservice is an architectural pattern that allows applications to consist of loosely coupled modules. However, microservices designs should follow these rules:
Each module or microservice has its own data and therefore should have an independent database.
Each microservice should be developed by its own team. This doesn’t mean that microservices-based applications can’t be developed by one team, however, having one team per microservice shows how independent microservices can be.
Microservices should have an independent deployment process.
Microservices should give better control over resources and compute power so you can scale each service independently and according to the service needs.
Microservices can be written in different languages, but they should communicate with a single protocol like REST.
Multi-cloud (i.e., hybrid cloud) means there are two different approaches to spreading an app through multiple cloud providers. For example, we can build core applications in AWS, and there are some parts that we can deploy to Azure and Google Cloud. Another multi-cloud example is when an app that was designed for one cloud can be migrated to another with minor changes.
Microservices vs. Monolith
Before we jump into examples, let’s look at a brief list of the pros and cons of microservices and monolithic architecture:
Scalability As we have all separate services, we can scale each service independently.
Isolation A large project may be unaffected when one service goes down.
Flexibility You can use different languages and technology as all services are independent. We can separate the whole project into microservices where each microservice will be developed and supported by a separate team.
DevOps independence All microservices are independent; therefore, we can implement an independent deployment process for each microservice.
Complexity Microservices architecture can be a good choice for huge, enterprise-level companies and platforms. Take Netflix, for example. There you can separate domains and subdomains to different services and teams. However, for a small-level company, separating may add redundant complexity. Moreover, it can be impossible to separate small projects into microservices.
Testing Testing microservices applications can be a difficult task as you may need to have other services running.
Debugging Debugging microservices can be a painful task; it may include running several microservices. Also, we need to constantly investigate logs. This issue can be partially resolved by integrating a monitoring platform.
When I start designing a solution, I always use monolithic architecture as the starting point. Using this approach, we can achieve the following:
During design and implementation, we can already see if our application can be moved to microservices.
We can identify monolithic applications that can be moved to microservices using a step-by-step approach.
Simplicity Monolithic architecture is relatively simple and can be used as a base architecture or the first microservice step.
Simple DevOps The DevOps process can be simple as we may need to automate one simple application.
Vendor lock-in With monolithic architecture, we may be locked with one vendor/ cloud provider. All modules in monolithic architecture are closely tied to each other. It’s hard to spread them across different vendors.
Inflexible DevOps The DevOps process for one enterprise-level, monolithic app can take a lot of time as we need to wait until all modules are built and tested.
Stick with one programming language/technology Monolithic architecture is not too flexible ‘ you need to stick with one technology/programming language. Otherwise, you must rewrite the whole application to change the core technology.
In Figure 1, you can see an example of a typical modular monolithic architecture of a traveling system. It allows passengers to find drivers, book, and join the trip.
Figure 1: Travel booking application
The system consists of several modules:
Stripe adapter to process payments
SendGrid to manage emails
Adapter for Twillio to be able to communicate over the phone (calls, SMS, video)
This monolithic architecture can be migrated to the microservices one. Each service can operate independently and can keep the state in its own database.
The solution explained above is quite simple and can be transformed into microservices without a lot of issues. However, when it comes to the enterprise level, this migration is far more complex, as is seen at large companies such as Netflix.
Cloud Components and Services for Building Microservices Architecture
In this section, we will get into a few cloud services that we can use to build microservices architecture so we know what service or cloud component to choose during microservices architecture design and implementation.
Container engines are essential to building microservices as they allow for separation, orchestration, and management of the microservices within various cloud providers. Docker is a widely used container engine that allows one to wrap each microservice into a container and put it into a cloud-based container orchestration system like Kubernetes (AKS, EKS) or directly spin up the application. Containerd is the same as Docker but has a lightweight and more secure architecture.
Kubernetes is a popular open-source system for orchestrating, automating deployment, and scaling the containers apps. It contains and automates the deployment. Azure, AWS, and Google Cloud have their own managed orchestration services that already include load balancing, auto scaling, workload management, monitoring, and a service mesh.
Azure Service Fabric is a distributed platform to orchestrate microservices. For several years, the main goal was to provide the best support for .NET Core/Framework- and Windows-based microservices; we can see it in the selection service flowchart. Microsoft claims that Service Fabric supports other languages and platforms like Linux as well.
A queue is a service that is based on the FIFO (first in, first out) principle. All message bus systems are based on this service. For example, Azure has queue storage that contains simple business logic. If you have a simple architecture that needs centralized message storage, you can rely on the queue. AWS and Google Cloud also have the Simple Queue Service and GC Pub/Sub, respectively. These allow you to send, store, and receive messages between microservices and software components.
Service Bus/Message Bus is based on the same approach as a queue. However, a Message Bus has more features on top ‘ it contains a dead-letter queue, scheduled delivery, message deferral, transactions, duplicate detection, and many other features. For example, Azure Service Bus and AWS Managed Kafka service are highly available message brokers for enterprise-level applications that can deal with thousands of messages.
Serverless allows us to build microservices architecture purely with an event-driven approach. It’s a single cloud service unit that is available on demand with an intended focus on building the microservices straight away in the cloud without thinking about what container engine, orchestrator, or cloud service you should use. AWS and Azure have Lambda and Azure Functions, respectively. Google Cloud Functions follows the same principle.
Essential Microservices Design Patterns
Now we understand microservices architecture and can start developing the application using microservices. We also understand the benefits of using microservices and how to refactor applications to support this architecture. The best point to start with building the app is to know microservices design patterns.
The microservices domain model (part of domain-driven design) is more than a pattern ‘ it is a set of principles to design and scope a microservice. A microservices domain should be designed using the following rules:
A single microservice should be an independent business function. Therefore, the overall service should be scoped to a single business concept.
Business logic should be encapsulated inside an API that is based on REST.
A legacy system may have unmaintainable code and an overall poor design, but we still rely on the data that comes from this module. An anti-corruption layer provides the facade, or bridge, between new microservices architecture and legacy architecture. Therefore, it allows us to stay away from manipulating legacy code and focus on feature development.
Figure 2: Anticorruption layer
The circuit breaker provides mechanisms to stop a cascade from failing and automatically recovering to a normal state. Imagine we have service A and service B that rely on data from service C. We introduce a bug in service C that affects services A and B. In a well-designed microservices architecture, each service should be independent of the other.
However, dependencies may occur during refactoring from monolith to microservices. In this case, you need to implement a circuit breaker to predict a cascade failure. Circuit breakers act as a state machine. They monitor numerous recent failures and decide what to do next. They can allow operation (closed state) or return the exception (open or half-open state).
Figure 3: Circuit breaker states
A service mesh implements the communication layer for microservices architecture. It ensures the delivery of service requests, provides load balancing, encrypts data (with TLS), and provides the discovery of other services. A service mesh also:
Provides circuit breaker logic
Provides the process to manage traffic control, network resilience, security, and authentication
Has a proxy that integrates with microservices using a sidecar pattern
It allows you to not only manage the service but also collect telemetry data and send it to the control plane. We implement a service mesh such as Istio, which is the most popular service mesh framework for managing microservices in Kubernetes.
Figure 4: Service mesh algorithm
Sidecar is a utility application that is deployed alongside the main service. Sidecar is helpful in:
Controlling connection to the service
Figure 5: Sidecar
Microservices in Action
To demonstrate the power of microservices, we will migrate our monolithic travel application (see Figure 1) to the microservices architecture.
We will use the serverless approach and Azure cloud.
API Gateway ‘ Using API Management to expose the endpoints of the back-end services so the client application can consume them securely.
Entry points ‘ The public-facing APIs that the client application will be using, powered by Azure Functions responding to HTTP requests.
Async queue ‘ Messaging service to handle services intercommunication and pass along information and data between the different services, represented by Azure Event Grid.
Backend services ‘ The services that are directly operating with the data layer and other components of the solution, isolated from the rest, and easily replaceable if needed.
Figure 6: Travel booking microservices
Building highly available, scalable, and performant applications can be challenging. Microservices architecture provides us the option to build not only independent services but also create several teams to support each service and introduce the DevOps approach. Microservices and all popular cloud providers allow us to build multi-cloud microservices architecture. This saves money, as some services have different pricing strategies. But be sure to choose the most appropriate service that’s suited for specific microservices domains. For example, we can use AKS with an integrated service mesh or a serverless approach based on AWS Lambdas. Multi-cloud allows us to apply cloud-native DevOps to deliver services independently.
This is an article from DZone’s 2022 Microservices and Containerization Trend Report.