Intro to Microservices Architecture for Business Leaders

microservices architecture design

Introduction

In today’s fast-paced and ever-evolving digital landscape, businesses are constantly seeking ways to enhance their agility, scalability, and innovation. The microservices architecture has emerged as a powerful solution to these challenges, offering a modular and scalable approach to application development. In this article, we will explore the key principles and strategies involved in microservices architecture design that drives business success.

Understanding Microservices Architecture

Microservices architecture design differs drastically from a monolithic design. Monolithic applications have dependencies that are highly connected and cannot be separated from each other. Microservices are exactly the opposite.

Microservices architecture – an architectural style that structures an application as a collection of small services, each running in its own process and communicating with lightweight mechanisms. 

By breaking down monolithic applications into smaller, independent services, businesses can achieve a range of benefits. These include: scalability, flexibility, fault isolation, resilience, independent deployment, and development. However, it’s essential to carefully consider the complexity and service boundaries before adopting a microservices architecture design.

Key Principles for Microservices Architecture Design

Several design principles play a crucial role in shaping an effective microservices architecture. 

The Single Responsibility Principle (SRP)

The single responsibility principle emphasizes that each service should have a single purpose, ensuring clarity and maintainability. 

Each microservice should be as simple as possible. When your services begin to go outside of their individual responsibilities, it becomes more difficult to troubleshoot, leading to longer delays in problem resolution or bug fixes.

Loose Coupling

Loose coupling helps minimize dependencies between services, enabling them to evolve independently. 

Each service should be independent from any other service in your application. A good test for this is to ask – “If my service were to change in design or implementation, would any other service be impacted?” If the answer is “yes” then you have a coupled system. 

To accomplish loose coupling, you often have to accept a certain level of duplication in your design. Developers who practice DRY (Don’t Repeat Yourself) principles might balk at this, but the goal is to have independent services not the most succinct implementation of libraries. Opt for more simple libraries that have a narrow focus over large, sprawling libraries that can “do it all.” 

For example:

Say we have an e-commerce application comprising of two microservices. One service handles orders and the other handles customers. In the image below, we have our two microservices sharing a common library with some irrelevant functions, depending on which service you’re looking at.

In the next image we’ve separated our shared library into two different libraries. As you can see the customer model is defined in both libraries.

It is possible to get carried away with this and there are some good use-cases for sharing a library (i.e. logging). The point is to approach coupling your microservices thoughtfully and understand the ramifications of your design choices.

Event-Driven Architecture

Event-driven architecture promotes loose coupling and scalability by facilitating asynchronous communication. 

Having services wait on one another can be time consuming and costly. To reduce the inherit coupling of synchronous communication, implement a message broker (Kafka/Celery/RabbitMQ) to transmit messages between your services. These messages are published to a queue and services pick up the messages as they need them. 

One thing to consider however is that as distributed microservices applications become more complex, so does the timing of the communication. Poorly designed applications will negate the benefits of asynchronous messaging with unnecessary waiting while dependencies are processed and published.

Domain-Driven Design (DDD)

DDD helps align services with business domains, fostering better understanding and collaboration. 

Domain-driven design advocates modeling based on the reality of business as relevant to your use cases. In the context of building applications, DDD talks about problems as domains. It describes independent problem areas as Bounded Contexts (each Bounded Context correlates to a microservice), and emphasizes a common language to talk about these problems.1

Organizing your microservices into “domains” that relate to areas of business, or application segments, can help your developers understand the problems they are trying to solve. Inside of each domain can be multiple microservices that each handle a separate segment of the domain. 

For example:

Let’s say you have a domain around user authentication in your application. You could have bound contexts that handle, logging in, user lookup, and user roles. Each could be a separate microservice that solves their individual business problem.

Command Query Responsibility Segregation (CQRS)

CQRS separates read and write operations, enhancing performance and scalability. 

In the world of microservices, it’s not uncommon for each microservice to have its own data store. But, as you begin to query that data and join it from several different data sources, it can become complicated and even lead to data integrity issues.

That’s where CQRS comes in. In a nutshell, CQRS is separating read and write operations for your data store. Read operations are handled through “queries” that can return data that is a different shape than that of write operations. Write operations, on the other hand, are task-based “commands” that handle the creating, updating, and deleting data in your data store(s). To increase efficiency, a message queueing system can be implemented to handle read/write operations asynchronously.

A CQRS design pattern can provide more performant data management but can also lead  to more complexity in your application. It’s important to note that, in this model, the read database’s data results from “eventual consistency.” Carefully consider the pros and cons before adopting it into your environment.

API Gateway and Service Discovery

Service discovery mechanisms, like API Gateways, simplify the interaction between clients and services.

How do clients know which microservice to interact with? What happens when you add or remove a microservice to/from your application?

The answer? An API Gateway.

With an API Gateway, the clients deal with the gateway and it relays the requests to the proper microservice. Two common uses of an API Gateway are to route/proxy requests to the appropriate service or to call multiple services with fewer requests.

There are many variations possible with an API Gateway. It’s important to consider all the ways an API Gateway can aid in the discovery of microservices in your application. It’s important to remember that this design principle should make it easier for clients to consume your services and not more complex.

Conclusion

There are many challenges facing businesses that want to adopt a microservices architecture design. As applications grow, they can become bloated and complicated, causing expenses and headaches to rise. With these design principles however, you can simplify your microservices application and take full advantage of the benefits it provides.

To learn how we can help you accomplish your business objectives contact us.

Footnotes

  1. https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/ddd-oriented-microservice