In the previous post in this series, we understood what is Saga Pattern. We also looked at the types of Saga Pattern implementation. Lastly, we also described the scenario we will implement using Saga Pattern.

Below is our high-level plan for this series about Saga Pattern implementation.

In Part 1, we went through the basics of Saga. If you are not sure about what is Saga Pattern, I strongly recommend you to go through that post and then come back to this post.

In Part 2 (this post), we will start by implementing Saga Pattern for a sample problem statement.

The next step or Part 3, we will continue with our implementation and connecting our services to the Axon Server.

Lastly, in Part 4, we will test our application and see the Saga Pattern Implementation in action.

The code for this entire application is available on Github for reference.

So let’s start with the problem statement once again.

The Problem Statement for Saga Pattern Implementation

As we discussed earlier as well, we will be implementing Saga Pattern using the Orchestration-based Saga approach. As a refresher, Orchestration-based Saga uses an orchestrator to manage the Saga.

In other words, an orchestrator is simply a manager that directs the participating services to execute one or more local transactions.

A typical example of this approach is shown in the below illustration.

orchestration based saga pattern implementation

Here, we are looking at Orchestration-Based Saga for solving the problem of Order Management. This Order Management problem can apply to any e-commerce store, food delivery apps, or any other similar use-case.

Of course, for demo purposes, we have made this problem much simpler than a real production level complexity.

High-Level Components

There are 5 major parts of this application with regards to our Saga Pattern Implementation. The parts are as follows:

  • Order Service – This service exposes APIs that help creating an Order in the system. Also, the service manages the Order Aggregate. Order Aggregate is nothing but an entity that maintains the Order related information. However, the Order Service also acts as the home for the actual Order Management Saga implementation.
  • Payment Service – The Payment Service acts upon the Create Invoice Command issued by the Order Management Saga. Once it finishes its job, it publishes an event. This event pushes the Saga forward onto the next step.
  • Shipping Service – This service takes care of creating a shipment in the system corresponding to the Order. It acts upon a command issued by the Saga Manager. Once it does it’s job, it also publishes an event that pushes the Saga forward.
  • Core-APIs – This is not a service as such. However, Core-APIs acts as the integration-glue between various services that form a part of the Saga. In our case, the Core-APIs will consist of the various commands and event definitions required for our Saga implementation to function
  • Axon Server – Axon Server is part of the Axon Platform. We will be using Axon Framework to manage our Aggregates such as Order, Payment, Shipping. Also, we will be using Axon Server to handle the communication between the three services. You can check out my post on in-depth view of Axon Server if you are looking for more details about it.

Let’s start with the implementation details:

Overall Application Structure

Below is the overall structure of our application.

.
├── core-apis
├── order-service
├── payment-service
├── pom.xml
├── saga-axon-server-spring-boot.iml
└── shipping-service

As you can see, it is a multi-maven module structure. Each service is a maven module and is part of the overall project.

The main POM.xml file glues all of them together.

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.progressivecoder.saga-pattern</groupId>
    <artifactId>saga-axon-server-spring-boot</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <modules>
        <module>order-service</module>
        <module>payment-service</module>
        <module>core-apis</module>
        <module>shipping-service</module>
    </modules>

</project>

Order Service Implementation

To implement Order Service (and all of the other services as well), we will create typical Spring Boot applications. If you are not aware of Spring Boot or want a refresher, refer to my post about Spring Boot Microservices.

Below are the dependencies for our application:

<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<!-- Axon -->
		<dependency>
			<groupId>org.axonframework</groupId>
			<artifactId>axon-spring-boot-starter</artifactId>
			<version>4.0.3</version>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>

		<!-- Swagger -->
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>2.9.2</version>
		</dependency>

		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>2.9.2</version>
		</dependency>

		<dependency>
			<groupId>javax.inject</groupId>
			<artifactId>javax.inject</artifactId>
			<version>1</version>
		</dependency>

		<dependency>
			<groupId>com.progressivecoder.saga-pattern</groupId>
			<artifactId>core-apis</artifactId>
			<version>${project.version}</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
</dependencies>

Some of the core things to consider in this are as follows:

  • We are using Axon Spring Boot Starter (version 4.0.3). This brings in support for Axon Framework plus Axon Server.
  • To make testing our application easy, we have also included Swagger.
  • We are using Spring Boot Starter Data JPA and H2 in-memory database for our persistence layer.
  • Also, we have another module called core-apis. We will come back to it in some time.

The Order Aggregate

Order Aggregate is one of the most important pieces of our Saga Pattern implementation. It forms the base around which the Order Management Saga will work. Let’s see how it looks like:

@Aggregate
public class OrderAggregate {

    @AggregateIdentifier
    private String orderId;

    private ItemType itemType;

    private BigDecimal price;

    private String currency;

    private OrderStatus orderStatus;

    public OrderAggregate() {
    }

    @CommandHandler
    public OrderAggregate(CreateOrderCommand createOrderCommand){
        AggregateLifecycle.apply(new OrderCreatedEvent(createOrderCommand.orderId, createOrderCommand.itemType,
                createOrderCommand.price, createOrderCommand.currency, createOrderCommand.orderStatus));
    }

    @EventSourcingHandler
    protected void on(OrderCreatedEvent orderCreatedEvent){
        this.orderId = orderCreatedEvent.orderId;
        this.itemType = ItemType.valueOf(orderCreatedEvent.itemType);
        this.price = orderCreatedEvent.price;
        this.currency = orderCreatedEvent.currency;
        this.orderStatus = OrderStatus.valueOf(orderCreatedEvent.orderStatus);
    }

    @CommandHandler
    protected void on(UpdateOrderStatusCommand updateOrderStatusCommand){
        AggregateLifecycle.apply(new OrderUpdatedEvent(updateOrderStatusCommand.orderId, updateOrderStatusCommand.orderStatus));
    }

    @EventSourcingHandler
    protected void on(OrderUpdatedEvent orderUpdatedEvent){
        this.orderId = orderId;
        this.orderStatus = OrderStatus.valueOf(orderUpdatedEvent.orderStatus);
    }
}

As you can see, this is a typical entity class. The major things to note here are the Axon specific annotations @Aggregate and @AggregateIdentifier. These annotations allow Axon Framework to manage the Order Aggregate instances.

Also, we are using Event Sourcing to store the events occurring on this aggregate. Event Sourcing is another Microservices Architecture pattern that deals with storing the aggregate information in the form of domain events. I have a detailed post on Event Sourcing implementation if you want to read more about it.

The Order Service

To facilitate creation of Orders, we have also defined an Order Service interface and its corresponding implementation.

public interface OrderCommandService {

    public CompletableFuture<String> createOrder(OrderCreateDTO orderCreateDTO);

}
@Service
public class OrderCommandServiceImpl implements OrderCommandService {

    private final CommandGateway commandGateway;

    public OrderCommandServiceImpl(CommandGateway commandGateway) {
        this.commandGateway = commandGateway;
    }

    @Override
    public CompletableFuture<String> createOrder(OrderCreateDTO orderCreateDTO) {
        return commandGateway.send(new CreateOrderCommand(UUID.randomUUID().toString(), orderCreateDTO.getItemType(),
                orderCreateDTO.getPrice(), orderCreateDTO.getCurrency(), String.valueOf(OrderStatus.CREATED)));
    }
}

This service implementation uses Axon Framework’s Command Gateway to issue a command to the Aggregate. The command is handled in the Aggregate class we declared earlier.

The Order Controller

The Order Controller class is the place where we create our API end-points. At this point, for the purposes of our demo, we have only one end-point.

@RestController
@RequestMapping(value = "/api/orders")
@Api(value = "Order Commands", description = "Order Commands Related Endpoints", tags = "Order Commands")
public class OrderCommandController {

    private OrderCommandService orderCommandService;

    public OrderCommandController(OrderCommandService orderCommandService) {
        this.orderCommandService = orderCommandService;
    }

    @PostMapping
    public CompletableFuture<String> createOrder(@RequestBody OrderCreateDTO orderCreateDTO){
        return orderCommandService.createOrder(orderCreateDTO);
    }
}

To help Swagger discover these end-points, we also configure Swagger using Docket Bean setup.

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Bean
    public Docket apiDocket(){
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.progressivecoder.ordermanagement"))
                .paths(PathSelectors.any())
                .build()
                .apiInfo(getApiInfo());
    }

    private ApiInfo getApiInfo(){
        return new ApiInfo(
                "Saga Pattern Implementation using Axon and Spring Boot",
                "App to demonstrate Saga Pattern using Axon and Spring Boot",
                "1.0.0",
                "Terms of Service",
                new Contact("Saurabh Dashora", "progressivecoder.com", "coder.progressive@gmail.com"),
                "",
                "",
                Collections.emptyList());
    }

}

The Order Management Saga

The heart of the Saga Pattern implementation is the Order Management Saga. In a nutshell, this is also a typical Java class that describes the various handlers for the individual Saga steps.

The individual Saga steps can be managed in a declarative manner. In other words, this makes it extremely easy for a developer to understand the flow of the Saga at a single-glance.

@Saga
public class OrderManagementSaga {

    @Inject
    private transient CommandGateway commandGateway;

    @StartSaga
    @SagaEventHandler(associationProperty = "orderId")
    public void handle(OrderCreatedEvent orderCreatedEvent){
        String paymentId = UUID.randomUUID().toString();
        System.out.println("Saga invoked");

        //associate Saga
        SagaLifecycle.associateWith("paymentId", paymentId);

        System.out.println("order id" + orderCreatedEvent.orderId);

        //send the commands
        commandGateway.send(new CreateInvoiceCommand(paymentId, orderCreatedEvent.orderId));
    }

    @SagaEventHandler(associationProperty = "paymentId")
    public void handle(InvoiceCreatedEvent invoiceCreatedEvent){
        String shippingId = UUID.randomUUID().toString();

        System.out.println("Saga continued");

        //associate Saga with shipping
        SagaLifecycle.associateWith("shipping", shippingId);

        //send the create shipping command
        commandGateway.send(new CreateShippingCommand(shippingId, invoiceCreatedEvent.orderId, invoiceCreatedEvent.paymentId));
    }

    @SagaEventHandler(associationProperty = "orderId")
    public void handle(OrderShippedEvent orderShippedEvent){
        commandGateway.send(new UpdateOrderStatusCommand(orderShippedEvent.orderId, String.valueOf(OrderStatus.SHIPPED)));
    }

    @SagaEventHandler(associationProperty = "orderId")
    public void handle(OrderUpdatedEvent orderUpdatedEvent){
        SagaLifecycle.end();
    }
}

Let’s understand what is going on here.

  • The @StartSaga annotation signifies the start of the Saga. It tells Axon to create a new Saga. The Saga is also associated with a particular instance of the Aggregate. This is done using the associationProperty specified with the @SagaEventHandler. In this case, we are associating the Saga with an instance of the Order Aggregate using the property orderId. We have also specified the event for which the method should be called. In our case, it is the Order Created Event.
  • The other methods annotated with @SagaEventHandler signify the other transactions that are part of the Saga.
  • We also associate the Saga with other concepts such as Payment and Shipping using SagaLifecycle.associateWith() method. By allowing the clients to generate the identifier, we don’t have to follow a request-response model. This allows us to easily associate an identifier with the Saga.
  • The Saga life-cycle is finished once we call the SagaLifecycle.end() method.

Core-APIs

Now would be a good time to look at the Core-APIs module.

The Core-APIs are nothing but a bunch of classes that define the Commands and Events that are going to be a part of our Saga Pattern Implementation.

Commands

The Create Order Command is triggered when a new Order is created in our application. This command is handled by the Order Aggregate.

public class CreateOrderCommand {

    @TargetAggregateIdentifier
    public final String orderId;

    public final String itemType;

    public final BigDecimal price;

    public final String currency;

    public final String orderStatus;

    public CreateOrderCommand(String orderId, String itemType, BigDecimal price, String currency, String orderStatus) {
        this.orderId = orderId;
        this.itemType = itemType;
        this.price = price;
        this.currency = currency;
        this.orderStatus = orderStatus;
    }
}

Next, the Create Invoice Command is triggered by the Order Management Saga when the Order is created.

public class CreateInvoiceCommand{

    @TargetAggregateIdentifier
    public final String paymentId;

    public final String orderId;

    public CreateInvoiceCommand(String paymentId, String orderId) {
        this.paymentId = paymentId;
        this.orderId = orderId;
    }
}

The Create Shipping Command is also triggered by the Order Management Saga when the invoice creation and payment processing is done.

public class CreateShippingCommand {

    @TargetAggregateIdentifier
    public final String shippingId;

    public final String orderId;

    public final String paymentId;

    public CreateShippingCommand(String shippingId, String orderId, String paymentId) {
        this.shippingId = shippingId;
        this.orderId = orderId;
        this.paymentId = paymentId;
    }
}

Lastly, we have the Update Order Status Command. When the shipping is done, this command is triggered.

public class UpdateOrderStatusCommand {

    @TargetAggregateIdentifier
    public final String orderId;

    public final String orderStatus;

    public UpdateOrderStatusCommand(String orderId, String orderStatus) {
        this.orderId = orderId;
        this.orderStatus = orderStatus;
    }
}

Events

The first event in the overall process is the Order Created Event. This event is also responsible for starting up the Saga as we saw earlier.

public class OrderCreatedEvent {

    public final String orderId;

    public final String itemType;

    public final BigDecimal price;

    public final String currency;

    public final String orderStatus;

    public OrderCreatedEvent(String orderId, String itemType, BigDecimal price, String currency, String orderStatus) {
        this.orderId = orderId;
        this.itemType = itemType;
        this.price = price;
        this.currency = currency;
        this.orderStatus = orderStatus;
    }
}

Next event is the Invoice Created Event. The Invoice Service publishes this event. We will see the implementation for the same in the next post.

public class InvoiceCreatedEvent  {

    public final String paymentId;

    public final String orderId;

    public InvoiceCreatedEvent(String paymentId, String orderId) {
        this.paymentId = paymentId;
        this.orderId = orderId;
    }
}

After that, we have the Order Shipped Event. This event is published by the Shipping Service after it has done the needful.

public class OrderShippedEvent {

    public final String shippingId;

    public final String orderId;

    public final String paymentId;

    public OrderShippedEvent(String shippingId, String orderId, String paymentId) {
        this.shippingId = shippingId;
        this.orderId = orderId;
        this.paymentId = paymentId;
    }
}

Lastly, we have the Order Updated Event. This event is published by the Order Aggregate after it has updated the status of the Order.

public class OrderUpdatedEvent {

    public final String orderId;

    public final String orderStatus;

    public OrderUpdatedEvent(String orderId, String orderStatus) {
        this.orderId = orderId;
        this.orderStatus = orderStatus;
    }
}

Conclusion

At this point, we have managed to come pretty far in implementing two major pieces of our application – the Order Service and the Core-APIs.

The Order Service houses our main Saga Pattern Implementation code. On the other hand, the Core-APIs are the backbone of our Order Management Saga.

We will end this post here since it has become quite long. However, the code for the entire Saga implementation is available on Github for reference.

In the next post, we will start implementing the other services and connecting them to the Axon Server. So stay tuned.


Saurabh Dashora

Saurabh is a Software Architect with over 12 years of experience. He has worked on large-scale distributed systems across various domains and organizations. He is also a passionate Technical Writer and loves sharing knowledge in the community.

4 Comments

DN · September 3, 2020 at 7:56 pm

Why did you created multi module project instead of creating different service interacting via http or messaging.
why can’t we have individual microservices instead of multimodule project ?

    Saurabh Dashora · September 11, 2020 at 2:15 am

    It was a conscious choice to make sharing of classes easy for demo purpose. However, you could go for different services as well just as easily depending upon your use case.

Sumit Ghosh · July 29, 2021 at 10:54 am

Where to perform the actual Repo/Dao layer operation?

Leave a Reply

Your email address will not be published. Required fields are marked *