In today’s technology, where software development is characterised by distributed systems and generally implemented in microservices, the adoption of patterns and designs for generic solutions seems almost a necessity. These patterns solve common problems such as migration from a monolith to microservices, coexistence of a legacy system with a current one, production staging, communication, resilience, scalability, etc.

Microservice architecture patterns offer a number of advantages and benefits for application design and development. These patterns are designed to address the specific challenges of building microservices-based systems.

Below, we propose a series of posts that will guide us through the different patterns that solve all these cases. The idea is to create an intermediate vision where it is clear what each point consists of (without going into too much detail, which would be long enough to write a book).

Today we are going to focus on architecture patterns focused on the organisation and structure of microservices.

What is microservices architecture?

Microservices architecture is a modern and flexible approach to application design. Instead of building a single monolithic application, we break it down into small independent services. Each service focuses on a specific task and communicates with other services through well-defined interfaces.

The idea is that each microservice is like a Lego piece that fits perfectly into the puzzle of the whole application. Each service can be developed, tested, deployed and scaled independently, facilitating agility and continuous software delivery.

A microservice can use its own technology and programming language, giving us the freedom to choose the best tool for the job. In addition, by having small, independent services, we can improve the scalability and availability of the system, as each service can be replicated on demand.

However, there are also challenges associated with microservices architecture. The complexity of managing multiple services, the communication between them, and the need to establish a robust infrastructure for monitoring and management are important aspects to consider in service governance.

In short, microservices architecture is an excellent choice for building modern, scalable applications. With proper planning and design, we can take full advantage of this architecture and deliver robust and adaptable solutions to today’s software development challenges.

When designing an application’s architecture, we need to think about the roadmap and what our future needs will be. You also need to be aware of the different types of microservices architectures and know what questions to ask when implementing them.

Let’s look at an example of a very simple basic microservices architecture. We will need to make changes depending on our needs.

microservices architecture

As you can see in the diagram, we have several elements:

The microservices architecture allows for service independence and scalability, where each box in the diagram represents a self-contained service that can be developed, deployed and scaled independently. In addition, the API Gateway acts as a façade for microservices and helps manage communication between clients and services.

MVC Pattern

The Model-View-Controller (MVC) pattern is one of the most well-known and widely used architectural patterns in web and desktop application development. MVC divides the application into three main components: Model, View and Controller. Each component has a specific function and is designed to maintain a clear separation of concerns.

Components of the MVC:

mvc controller

In a Spring application, this would be an example:

mvc controller spring application example

Model:

package com.ecommerce.model;

public class Product {
    private Long id;
    private String name;
    private double price;

    // Getters y setters
}
package com.ecommerce.repository;

import com.ecommerce.model.Product;
import org.springframework.stereotype.Repository;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Repository
public class ProductRepository {
    private final Map<Long, Product> products = new HashMap<>();

    public Product findById(Long id) {
        return products.get(id);
    }

    public List<Product> findAll() {
        return new ArrayList<>(products.values());
    }

    public void save(Product product) {
        products.put(product.getId(), product);
    }
}

View:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <title>Detalles del Producto</title>
</head>
<body>
    <h1>Detalles del Producto</h1>
    Nombre: ${product.name}
    Precio: ${product.price}
</body>
</html>

Controller:

package com.ecommerce.controller;

import com.ecommerce.model.Product;
import com.ecommerce.repository.ProductRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@Controller
public class ProductController {
    private final ProductRepository productRepository;

    public ProductController(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    @GetMapping("/product/{id}")
    public String getProduct(@PathVariable Long id, Model model) {
        Product product = productRepository.findById(id);
        
        model.addAttribute("product", product);
        return "product"; // Nombre de la vista (product.jsp)
    }
}

In this example, the ProductRepository is responsible for storing and managing the products. The ProductController communicates with the repository to retrieve the product data and pass it to the view.

Note that this is a very simple example and that, in more complex applications, you may use real databases instead of an in-memory data structure.

MVC is traditional and is being replaced by more modern and versatile architectures where the front end is completely decoupled. It often appears in architectures as a source to migrate to a microservices architecture.

Clean Architecture

When talking about architectural patterns, it is very useful to mention the concept of clean architecture. Clean Architecture is a software design pattern that aims to produce applications with clean, well-structured and maintainable code. It was proposed by Robert C. Martin also known as ‘Uncle Bob’. The main objective of this architecture is to separate the different layers of an application, so that each has a clearly defined responsibility and is decoupled from the others.

Clean Architecture follows the principle of UI (User Interface) Independence, which means that the innermost layers are nor dependent on the outer layers. This means that changes to the user interface or technical details do not affect the core business logic, making the application more flexible and easier to adapt.

The main layers of Clean Architecture are:

Clean architecture structure

Suppose we are building an e-commerce system based on microservices. Each microservice represents a specific functionality, such as product management, order processing and user authentication.

We are going to apply Clean Architecture to this situation, taking into account that we are going to implement the product management microservice:

  1. Entities: we define the “Product” class, which represents a product in the catalogue.
public class Product {
  private String name;
  private double price;
  private int stock;
  // Getters and setters 
}
  1. Use Cases: we create the use cases for each functionality of the application.
public interface AddProductUseCase {
  void addProduct(Product product); 
} 
public interface PurchaseUseCase {
  void purchaseProduct(Product product, int quantity); 
} 
public interface ViewAllProductsUseCase { 
 List<Product> getAllProducts(); 
}
  1. Interface Adapters: we implement the adapters that communicate with the use cases from the user interface.
public class ProductController { 
  private AddProductUseCase addProductUseCase; 
  private PurchaseUseCase purchaseUseCase; 
  private ViewAllProductsUseCase viewAllProductsUseCase;  

//Constructor e inyección de dependencias 

  @PostMapping("/products") public 
  ResponseEntity<String> addProduct(@RequestBody Product product) {
    addProductUseCase.addProduct(product); 
    return ResponseEntity.ok("Product added successfully");
  }

  @PostMapping("/purchase") 
  public ResponseEntity<String> purchaseProduct(@RequestBody PurchaseRequest purchaseRequest) {
    Product product = getProductById(purchaseRequest.getProductId());
    purchaseUseCase.purchaseProduct(product, purchaseRequest.getQuantity()); 
    return ResponseEntity.ok("Purchase successful"); 
} 

  @GetMapping("/products") public ResponseEntity<List<Product>> getAllProducts() {
    List<Product> products = viewAllProductsUseCase.getAllProducts();
     return ResponseEntity.ok(products); 
  } 
}
  1. Frameworks and Drivers: we use a database to store the products and an adapter to interact with it.
@Repository
public class ProductRepositoryImpl implements ProductRepository {
    private List<Product> productList = new ArrayList<>();
    
    @Override
    public void save(Product product) {
        productList.add(product);
    }

    @Override
    public List<Product> findAll() {
        return productList;
    }
}

Clean Architecture gives us a clear organisation of our application. Use cases and entities are isolated from the technical details and user interface, giving us the flexibility to make changes and improvements without affecting other parts of the application.

Clean Architecture brings several significant benefits to the design and development of microservices in a system. These benefits come from its focus on separating of responsibilities and creating modular and maintainable code. Here are some of the ways in which Clean Architecture adds value to microservices:

Clean Architecture provides a solid foundation for the design and development of microservices by promoting modularity, decoupling and flexibility. These attributes are critical to creating microservices that are maintainable, scalable and adaptable in an ever-changing environment.

Database per Microservice

The Database per Microservice pattern is an architectural strategy that suggests that each microservice has its own independent and dedicated database. Instead of sharing a single centralised database, each microservice has its own schema and database, allowing microservices to be more autonomous and avoiding coupling between them in terms of data storage.

This approach aims to achieve greater independence and scalability between microservices. Each microservice can choose the technology and type of database best suited to its purpose, without impacting the other services. In addition, the dedicated database facilitates individual scaling of microservices according to their load and data access needs.

The Database per Microservice pattern has several advantages:

Despite the benefits, the Database per Microservice approach has its challenges and considerations:

Let’s look at an example:

EDatabase per Microservice example

Suppose we have several microservices in an e-commerce application. We will continue with the example of an e-commerce.

In this example, we will see what reasons would lead us to choose one database or another; there is not always a single good solution, so everything is debatable and can change depending on the requirements of the type of application. Another important point when making decisions is the cost, having different databases, licences, clusters can make a project economically unviable, so it is also possible to grow as the need arises. Let’s not forget that we are giving an example based on the pattern described.

Each microservice can choose the database technology that best suits its needs. This allows development teams to work independently and prevents changes in one microservice from impacting the database of another. However, if combined product and order information is required, a data integration strategy must be implemented.

In summary, the Database per Microservice pattern is a strategy that provides independence and scalability to microservices, but also introduces challenges of consistency and data management. The choice of this pattern should be based on the specific needs and requirements of the application.

The Bottom Line

In this post we have taken a first approach to microservices architecture. We have reviewed what it is, its advantages and what it can do for us. In addition, we have discussed some of the microservice architecture patterns such as MVC pattern, Clean Architecture or Database per Microservice.

But there is much more to come:

We will review the organisational architecture patterns and structure of microservices, the communication and coordination of microservices and, later on, we will look at DDD, Hexagonal Architecture, as well as what they can contribute in conjunction with Clean Architecture or Serverless.

Tell us what you think.

Comments are moderated and will only be visible if they add to the discussion in a constructive way. If you disagree with a point, please, be polite.

Subscribe