Hero Image

gravity9 System Architecture Guide: Microservices

 

05 Feb 2024 | Przemyslaw Serwicki

Mastering Microservices: A Comprehensive System Architecture Guide

Architecture is a vital aspect of all systems, and applications, serving as a blueprint for engineers which defines behaviour and structure. There are numerous architectural patterns out there and they can be used on different levels of a system. In fact, a single system can use multiple architectural patterns!

As a digital consultancy, at gravity9 we have a rich history and heritage of development, picking the best architecture for the job.

In this series of articles, we’ll introduce some of the most popular system architecture around. We’ll look at why they’re popular, where they’re useful and where they’re less useful.

What Are Microservices?

Microservices are a hot trend among architectural patterns – associated with scalability and flexibility – microservices (aka microservices architecture) is an architectural style where a more extensive application is broken down into smaller, self-sufficient units capable of functioning independently.

These independent units are called services, which exhibit the following characteristics:

  • Independence in deployment.

  • Loosely coupled relationships.

  • Maintainability and testability.

  • Organization around distinct business capabilities.

  • Ownership is manageable by a single team.

Here’s an example of a simple system built on microservices architecture (for simplicity’s sake the diagram only uses the synchronous communication pattern):

In essence, rather than relying on a single extensive application housing all product functionalities, microservices architecture employs small, interconnected software units. These units operate independently and communicate seamlessly.

This empowers organizations to deliver comprehensive and intricate systems capable of receiving frequent updates without risking a complete system failure during a single release.

Perfect For Everything?

In today’s tech landscape, ‘microservices’ has become a buzzword, prompting the question: “Should every project be designed using microservices architecture?”

To answer that question, it’s essential to compare microservices to monolithic architecture. While microservices comprise smaller, independent services, a monolith functions as a single system, necessitating the full release of the entire system, even for minor updates.

While monolithic architecture may seem less glamorous and “significant”, it does offer advantages:

  • It facilitates enhanced team collaboration in development and deployment.

  • It can shorten and simplify testing.

  • It streamlines the debugging process.

  • It enables seamless system communication.

These advantages of the monolithic approach, contrasted against the challenges of microservices, underscore compelling reasons to consider adopting a monolithic architecture.

There is reduced cost, effort, and a lower entry threshold and for these reasons, projects often commence as monoliths before considering a potential migration to microservices. The most important action there is to monitor the progression of the monolithic system, frequently re-evaluating if a monolithic approach is fit for purpose, or if a move to microservices would be more advantageous.

The short answer to our question then, is “it depends”.

If and When?

You might now be wondering if a migration from monolithic to microservices architecture is required, and when the right time might be?

The answer often relies on recognising specific systems and understanding the challenges and benefits offered by both monolithic and microservices architectures.

The presence of these challenges in a Monolithic system could serve as a warning flag, indicating a possible need to migrate to microservices architecture:

  • Team Collaboration Difficulties: Microservices facilitate organising larger teams into smaller, focused sub-teams, each dedicated to different system parts. This allows developers or teams to focus on one problem at hand, enhancing collaboration and efficiency.

  • Language flexibility: Microservices allow for the use of different programming languages suited to specific tasks, such as Python for AI and LLM workloads. This flexibility in language choice enhances the overall capabilities of the system and supports diverse business requirements.

  • Lengthy / High-Risk Release Cycles: Unlike monolithic systems, microservices enable deployment by component, allowing for smaller and more manageable releases, with reduced risk.

  • Scalability Challenges: Specific areas of the system experiencing higher traffic can be individually scaled using the microservices approach, with resources and funding focussed where it’s needed most. Additionally, microservices architecture allows individual services to scale independently, optimizing resources and infrastructure where needed.

  • Allowing Modernization and Evolution: Microservices architecture allows for modernizing and evolving services independently, compared to monolithic architecture where such evolution may be limited. This is provided that contract design and compatibility are managed properly.

  • Error Isolation: In microservices, errors during the deployment of a particular service affect only that specific part of the application, preserving the functionality of the rest. In contrast, an individual error within a monolithic architecture can potentially affect the entire application’s availability!

Microservices Designed Right.

Having established that microservices architecture is the right fit, how do you start designing it?

The truth is that there is ‘no one size fits all’ solution – as every business case is different – so first, business needs must be established, and operations segmented to define product subdomains.

This preliminary exercise lays the groundwork for crucial design elements, including:

  • Identifying services.

  • Defining their responsibilities.

  • Designing APIs.

  • Mapping service dependencies.

  • Establishing service communication.

  • Handling data migration.

  • Deploying and hosting microservices separately.

  • Investing in observability.

It’s important to note that the migration need not happen all at once.

The transition from monolith to microservice can begin with just one microservice separated from the monolith, allowing for insightful observations, stability and error checking that will provide security when other components start to be migrated.

While this article won’t delve into the specifics of the migration process, numerous valuable resources and patterns, such as the Strangler pattern shown in the image above, provide guidance on this front.

Microservices Advantages.

We’ve covered what microservice architecture is, and what challenges it can bring (as well as a little about monoliths). Next, we’ll look at the pros and cons of microservices, starting with the benefits:

  • Improved Scalability: The flexibility to precisely scale selected components based on specific requirements and observations.

  • Independent Deployment: Individual microservices allow separate deployments. Teams can focus on specific releases, addressing issues within a service without impacting the entire application.

  • Diverse Teams and Technologies: Each microservice can be handled by separate teams, employing different technologies. This flexibility reduces development effort, facilitates parallel work, and accommodates diverse tech stacks based on individual subdomain needs.

  • Enhanced Resiliency: Microservices operate independently, ensuring application continuity even if one service encounters issues, unlike a monolithic system where a fault anywhere can halt the entire application.

  • System Boundaries: While defining system boundaries in microservices can be advantageous and challenging, setting clear boundaries can ease domain modifications. Although this may pose difficulties in moving parts between services in the future, the delineation of boundaries simplifies incremental modifications within independent components.

Microservices Disadvantages.

Next, let’s look at some potential drawbacks to microservices architecture:

  • Performance Challenges: Microservices’ need for inter-communication can impact performance due to network latency and message processing. These services, although independent, may require coordination to provide requested end-user information.

  • Complex Testing: Each service functions as a separate entity which demands distinct unit, component, and integration testing among multiple services. Ensuring seamless interactions between services adds complexity (and therefore time and manpower) to the testing process.

  • Distributed Logging Complexity: Individual services maintain separate logging mechanisms, resulting in a large volume of distributed log data. Structuring and organizing these logs are crucial for identifying and resolving issues within a specific operation.

  • Transaction Management Complexity: Distributed transactions spanning multiple services and databases pose challenges. A failure within one service can disrupt an entire transaction, requiring meticulous preparation and strategic solutions.

  • Deployment Challenges: Deploying microservices involves separate CI/CD processes, orchestration of infrastructure and configuration for the entire system, making deployment more intricate. However, in case of deployment issues, only individual services are affected compared to the entire monolithic application.

Understanding both the advantages and disadvantages aids in making an informed choice when considering microservices against simpler and more potentially cost-effective architectural options.

How Do Microservice Elements Communicate?

Before we can discuss an extensive system fit for an end user, it’s important to understand how elements can communicate. The answer can be one of two categories: synchronous and asynchronous.

Synchronous Microservices Communication:

Synchronous communication uses HTTP or gRPC protocol to return a sync response. The client sends a request and waits for a response from the requested service. Under the hood, client code blocks their execution thread until the server returns the response.

One of the most popular examples is synchronous communication with REST API, which can look like this:

  • The client sends a request using HTTP protocol and waits for a response from the service.

  • The client’s code will continue its tasks until it receives the HTTP server response.

HTTP protocols and REST approaches are the most common ways to design APIs, especially if we’re exposing APIs to the outside of the microservice cluster.

The downside of services communicating synchronously is that there are extra HTTP requests between them, which cause those services to be “connected“ to each other. To have a more loosely coupled solution, we should take a look at another approach, which is asynchronous.

Asynchronous Microservices Communication:

Unlike HTTP communication (as mentioned in synchronous communication above), the services involved in this architecture do not directly communicate. Instead, the services push messages to a message broker that each of the other services is also subscribed to, eliminating a lot of the complexity associated with HTTP communication.

With asynchronous communication, we don’t have to know anything about other services as they do not call each other directly. Instead, all services know a message broker and push messages to that broker. Other services can choose to subscribe to the messages in the broker (for those messages they care about).

A working example of asynchronous communication in microservices can look like this:

  • The client sends a message, but it doesn’t wait for a response from the service.

  • The subscriber system consumes this message in an asynchronous way – nothing is waiting for a response by blocking its execution (the client should not have blocked a thread while waiting for a response).

Asynchronous systems can be implemented in one-to-one (queue) or one-to-many (topic) modes.

  • In a one-to-one (queue) implementation, there is a single producer and consumer.

  • In one-to-many (topic) implementation, there are multiple consumers. Each request can be processed by zero or multiple consumers.

Both approaches describe communication across microservices architecture. Where synchronous communication relies on direct interaction between services like HTTP requests, asynchronous communication utilizes a message broker to facilitate indirect communication between services, reducing coupling and allowing for more scalable and fault-tolerant interactions.

In asynchronous communication, the message broker acts as an intermediary, decoupling the producers from the consumers. This decoupling enables independent redeployment of both producers and consumers, ensuring zero downtime during deployments. Furthermore, the publish/subscribe model allows for seamless addition of subscribers over time, enhancing scalability. When integrated with Event Driven Architecture, this decoupled communication model becomes even more powerful, enabling efficient and flexible interactions between microservices.

Exploring Real-World Microservices Use Cases.

Here are a few examples where microservices can (and have been) used:

  • Media Content: Netflix, HBO Go, and Amazon Prime Video handle billions of API requests daily, leveraging microservices for scalability to manage these requests based on subdomains.

  • Big Data: Different services manage each task of the data lifecycle, enabling loosely coupled operations for data collection, processing, storage, and delivery.

  • Rapidly Growing Applications with Large User Numbers: Microservice-based applications efficiently handle a large number of users and simultaneous requests, ensuring better performance through parallel operations. Efficient scalability is the main factor here!

  • Legacy Solution Migration/Updates: Microservices facilitate smooth execution of migration plans by gradually decoupling various business areas, avoiding risky major system upgrades.

  • Transactional Systems: Microservices offer a safer approach for critical transactions, such as payment functionalities. In an application failure scenario, especially in systems containing payment functions, microservices’ decoupled nature ensures safety. This means that modifications can be made in different parts of the system without directly affecting the most critical area, safeguarding vital functionalities.

How Does gravity9 Work with Microservices?

American Homes for Rent (AMH), a long-standing client and rental property provider, engaged in a collaborative partnership with gravity9 around 2020. Right from the start, our involvement in their architectural review process granted us invaluable insights into specific operational challenges being faced.

They were reliant on external systems to manage customers and properties, alongside their web application that catered to all business use cases. They had developed their own API for interfacing with these external systems, however several complexities emerged as the product expanded:

  • Data Dependency and Integration Complexity: The client’s architecture was tightly coupled with external systems such as Customer Relationship Management (CRM) and Property Management System (PMS), resulting in dependencies that hindered flexibility and made it challenging to replace or upgrade these systems seamlessly.

  • Lack of Control: Domain logic residing in external systems or integrations hindered direct control over their product.

  • Scalability Issues: Thanks to popular features attracting more clients, a need to scale external systems as a single unit emerged, making scalability a formal, less flexible process.

  • Alignment of New Ideas: New business needs had to conform to the limitations of the underlying system, impeding flexibility and adaptability. With AMH reliant on third-party providers (their resources and constraints), they found themselves restricted in autonomy to innovate and grow.

With these issues identified, gravity9 started planning a new solution where we needed to reduce the coupling to external systems, in order to prepare for the gradual replacement of responsibility and finally their removal.

Following a deep analysis of the core domain and subdomains, microservices architecture would be implemented on Kubernetes (a useful container orchestration system that helps to automate software deployment and management), starting with property and customer implementation – the foundation and core of the microservices.

As work continued, new requirements arrived. In addition to client-facing web applications, gravity9 also implemented web applications for back-office and vendor support. Thanks to the implementation of microservice architecture, plugging in new components became a much easier task.

The project now covers multiple applications (implemented in micro-frontend architecture), a large microservices implementation and a set of integrations with 3rd party libraries and providers. This has been made easier thanks to the scalability and flexibility of microservices.

Let’s take a look under the hood, at how this work can be better visualised.

AMH’s platform consists of applications tailored for vendors, employees, and customers (tenants), presented through a unified UI. Each user interface is composed of multiple micro frontends, each one addressing a specific business domain or context. These micro frontends leverage robust backend architectures.

Our backend-for-frontend approach employs RESTful APIs, serving as intermediaries that retrieve data from proper data sets rather than storing it. This architecture necessitates a transition toward microservices, which act as independent engines, each equipped with a microDB for direct interaction.

Communication within the platform encompasses both synchronous and asynchronous methods:

  • Synchronous communication involves APIs responding to client requests by querying the necessary services to generate valuable responses.

  • Asynchronous communication occurs when a domain operation within a microservice triggers the publication of an event, describing the action taken. Other microservices interested in this domain consume these events to initiate appropriate actions within their respective subdomains.

It Works with People, too!

Technical aspects aside, microservices are also influenced by the organizational structure of the development teams. Consider that we have:

  • Multiple web applications for customers, AMH back office, vendors and technicians…

  • Many APIs and microservices that are domain engine under the hood…

  • And other integrations.

We have split people working on the project into smaller workstreams, providing multiple benefits:

  • Smaller, easier-to-manage teams.

  • Teams interrupt each other less.

  • We can focus people by particular technical skills when organizing new workstreams.

  • People can jump into workstreams more quickly, as they do not have to know one extensive application. They begin with focus on the specific workstreams’ functionality.

Clearly, microservices are a valuable pattern that makes work more straightforward to manage from a technical perspective and an organisational one – and helps define team structure to our advantage.

Is Microservices Right for YOU?

We’ve described what microservices are, the pros and cons as well as where they may have drawbacks and advantages over the obvious alternative – monolithic architecture. A monolithic approach may make more sense in the short term, when a project is smaller and more easily manageable, but there are significant advantages to moving towards microservices as certain growing pains emerge.

It’s up to you to take an introspective look at your system, analyse it, and determine if it’s worth investing the time and money into a more complex microservices solution (or to call on someone who can!).

Sometimes, especially with a straightforward system or business challenge, a simple monolithic architecture is sufficient. However, if the aforementioned growing pains are starting to show, or you’re expecting to need to scale in size or complexity, it’s worth considering microservices architecture for your needs.

New technical expertise and even a new team might be necessary – but as we’ve shown in our gravity9 work with AMH – there can be significant benefits to such a move, or engaging a consultant such as gravity9 who can help you.

Why not check out other articles in our architecture series to learn more about your options, or get in touch for a more tailored review of your options?