HomeBlogSoC, Spring Boot and Microservices
Gianfranco Cautiero

SoC, Spring Boot and Microservices

Introduction

An overview on SoC principals for 3-tier web applications and microservices. The article will also show how SoC is a natural way of thinking in building systems with microservices. SoC (Separation Of Concerns) is an important design and architectural principle: every element of a software application — a component, a layer, a package, a class, or a method should have one concern and implement it well. 

Let’s see how SoC pattern applies to the design and development of a web application. Typically software applications have layers of abstraction.

In designing the interfaces of the above software layers we need different kinds of objects, properties, constants, etc. Let’s introduce some Domain Driven Design theory in order to identify our main actors. The work in the Domain-Driven Design community helps us deal with boundaries within our domain.

A data transfer object (DTO) is an object that is just a simple data container, and these objects are used to carry data between different processes and between the layers of our application. Such an object will help to not break the interfaces on every model change.

A more straightforward approach is to return entities and values object to the web layer. Here some reasons why this in general sounds like not the best idea:

DTOs can also add a lot of maintenance overhead, especially for simple domain objects. We’re not all writing always huge systems, sometimes we’re building fairly simple applications that is better to keep as simple as possible. (Everything should be made as simple as possible, but not simpler right? 🙂 ) But, as a domain becomes more complex or when a client of the service layer needs a different, or restricted view of data, then the use of DTOs become rather essential.

Designing a web application with Spring Boot

Spring has an annotation-based dependency injection mechanism that automatically scans and makes instances available (as Spring beans) for all classes using @Component. Furthermore, @Service, @Controller, and @Repository are offered and are nothing but a specialized form of @Component annotation for certain situations. @Service and @Repository annotation, they are a specialization of @Component in service and persistence layer, respectively. @Service will be used to hold business logic and @Repository will be used to implement a JPA repository to read and write entities (@Entity annotation used) in the data storage. @Controller is used to annotate classes implementing REST endpoints. This means that the different annotations are the same with respect to bean creation and dependency injection, but later Spring will treat those instances differently. For example, DispatcherServlet will look for @RequestMapping on classes that are annotated using @Controller but not with @Component.

Basically, Spring framework helps to define the domain with several components, to expose information with Services and Controllers and to persist them with Entity and Repository. This gave us good support in applying concepts from SoC and DDD to our software design/implementation. In order to fix better these concepts, let’s say we want to implement a web app to manage orders.

  1. Create entity person to map a certain data storage resource (e.g. DB tables) with a unique id, and create CRUD repository with save(), get(), getAll(), etc
  2. Create services to expose the operation implemented by the CRUD repository. For example, we can think to have CreateOrder() method which internally will call save().
  3. The components of this service layer will return DTO’s.
  4. Create rest controllers (get, post, put, patch, delete) to modify the state of our domain objects. The methods here are dealing just with DTOs.
  5. To avoid having to write boilerplate code to map DTOs into entities and vice-versa, it is possible to use ModelMapper library.
  6. Keeping scalability (and microservices) in mind a natural way to structure the application is to have 2 modules one implementing the repository layer and service layer and another implementing the web part. In this way, the web application (REST endpoints, Html templates, js) can use the functionality that the service exposes hiding domain and storage-specific concepts.

Microservices are prime examples of separation of concern

All the microservices shown are part of a movie application. But each one of them has its own independent concern. Separation of concerns is also an important factor in building common application and infrastructure components. Since a large part of the functionality of microservices is common to all of them, it makes sense to extract out the common components. Here is an example:

All microservices need features such as security and logging. By identifying common features in your application, such as Security and Logging, and providing well-defined interfaces with each microservice, the implementation of the microservices gets simpler. The same is the case with common infrastructure components. Any microservice can interact with them and use their functionality. Each microservice needs to have and own a single function or purpose. Additionally, if your microservice is data-based, ensure that it owns that data, and exists as the sole access point for that data. When there are scalability concerns that require a separate read model, you may end up with different services for reading and writing, so different data access points. Actually, microservices are a hot topic today together with discussions on how to migrate monolith applications in a set of services.

Axon Framework

Axon provides a way of developing Java applications that can evolve without significant refactoring from a monolith to Event-Driven microservices. Axon uses concepts from DDD and is based on CQRS and Event Sourcing. CQRS is an architectural pattern that distinguishes between two parts of an application:

Aggregate: a group of associated objects which are considered as one unit with regard to data changes.

Event: a notification that something relevant has happened inside the domain. Basically, a notification that a change has taken place.

Example: Flight is delayed

Command: Expression of an intent to change the application’s state.

Query: Request for information. Usually answered with Data Transfer Objects.

So basically all the communications, and updates are performed asynchronously via commands and events. The state information is accessible only via queries.

LAB: Application for placing orders using Axon

Let’s imagine building a simple application with Axon that focuses on an Order domain. First, we’ll define the Commands, meaning the expressions of intent. The Order service is capable of handling three different types of actions:

Every command needs to have a TargetAggregateIdentifier. The TargetAggregateIdentifier annotation tells Axon that the annotated field is an id of a given aggregate to which the command should be targeted. Usually, you want to mark such a field in the commands as final. This is intentional, as it’s a best practice for any message implementation to be immutable. Axon introduces an Aggregate annotation which is an Axon Spring-specific annotation marking this class as an aggregate. External references are restricted to one member of the Aggregate, designated as the root. A set of consistency rules applies within the Aggregate’s boundaries. The aggregate will handle commands which are targeted for a specific aggregate instance.

For our example will define and OrderAggragate. Its life cycle starts with PlaceOrderCommand in the OrderAggregate constructor. To tell the framework that the given function is able to handle commands, we’ll add the CommandHandler annotation. When handling the PlaceOrderCommand, it will notify the rest of the application that an order was placed by publishing the OrderPlacedEvent.To publish an event from within an aggregate, we’ll use AggregateLifecycle#apply(Object…). From this point, we can actually start to incorporate Event Sourcing as the driving force to recreate an aggregate instance from its stream of events. We start this off with the ‘aggregate creation event’, the OrderPlacedEvent, which is handled in an EventSourcingHandler annotated function to set the orderId and orderConfirmed state of the Order aggregate. Also note that to be able to source an aggregate based on its events, Axon requires a default constructor. Following code example and implementation notes. In the following example the aggregate with the command and event needed for construct the object


@Aggregate
public class OrderAggregate {

    @AggregateIdentifier
    private String orderId;
    private boolean orderConfirmed;

    @CommandHandler
    public OrderAggregate(PlaceOrderCommand command) {
        AggregateLifecycle.apply(new OrderPlacedEvent(command.getOrderId(), command.getProduct()));
    }

    @CommandHandler
    public void handle(ConfirmOrderCommand command) {
        AggregateLifecycle.apply(new OrderConfirmedEvent(orderId));
    }

    @CommandHandler
    public void handle(ShipOrderCommand command) throws UnconfirmedOrderException {
        if (!orderConfirmed) {
            throw new UnconfirmedOrderException();
        }
        AggregateLifecycle.apply(new OrderShippedEvent(orderId));
    }

    @EventSourcingHandler
    public void on(OrderPlacedEvent event) {
        this.orderId = event.getOrderId();
        orderConfirmed = false;
    }

    protected OrderAggregate() { }
}
@Data
@Getter
public class PlaceOrderCommand {

    @TargetAggregateIdentifier
    private final String orderId;
    private final String product;

    public PlaceOrderCommand(String orderId, String product) {
        this.orderId = orderId;
        this.product = product;
    }

    // constructor, getters, equals/hashCode and toString
}
@Data
public class OrderPlacedEvent {

    private final String orderId;
    private final String product;

    public OrderPlacedEvent(String orderId, String product) {
        this.orderId = orderId;
        this.product = product;
    }
}

Notes:

Axon Server can be used Event Store and our dedicated command, event and query routing solution. As a Message Routing solution, it gives us the option to connect several instances together without focusing on configuring things like a RabbitMQ or a Kafka topic to share and dispatch messages.

Final consideration

In a traditional way of storing an application’s state, we capture the current state and store it in some relational or NoSQL database. While this approach is really straightforward, it does not provide a way of deducting how we got to that current state. Of course, one may argue that we can have a separate model for keeping the history of actions that lead to the current state, but besides the additional complexity, these two models could easily go different paths and start being inconsistent. Axon gives us a way of storing an application’s state through the history of events that have happened in the past. The current state is reconstructed based on the full history of events, where each event represents a change or fact in our application. Events give us a single source of truth about what happened in our application. It is especially beneficial in applications that need to provide a full audit log to external reviewers (or in general in applications dealing with sensitive data where the history is essential to the domain itself). In my experience Axon can give you many benefits in building a large software system based on Microservices. It will help to make your services communicate in an asynchronous way, usually using Axon server or RabbitMQ plus an event store of your choice. It will help also to achieve the separation of concern discussed earlier in this article. With such a model, there is no point-to-point interaction in the system, every concern can be dedicated and the state is updated consistently with commands and events. The read part as we saw is left to a separate query module. On the flip side, all this imposes a precise way to structure your software (as you can see from the code snippet above) so if the choice to use it won’t be consistent over time it’s not a great idea to start at all. More, if the history of state changes is not important to your domain you can think to use another solution (or maybe a ‘homemade’ one) just to handle asynchronous communication. The benefit of a scenario like this is that you can structure and maintain your software in a more independent and ‘free’ way. Event sourcing can also be achieved with Kafka as discussed in Christian Posta article (see links below). Microservices are a hot topic today together with discussions on how to migrate monolith applications in a set of services.

What is next?

I suggest the following articles in order to dive deeper into discussions about microservices.

Time to Move to a Four-Tier Application Architecture – NGINX

Microservices For Greenfield?

The Hardest Part About Microservices: Your Data

Gianfranco Cautiero

Senior Software Developer