Saga State Machine & MassTransit Automatonymous & Request Response Pattern

What is the Saga Pattern?

Barış Can Tanrıverdi
adessoTurkey
5 min readJan 17, 2023

--

The Saga is a pattern for managing processes and failures on distributed services. It enables the coordination of transactions between several services to provide consistency in long-running business processes. The Saga simplifies event flow and is the central point of truth for a specific workflow. The saga must know to what process the message belongs. Thus, it must be obvious how a message is correlated to a particular process by CorrelationId. This article will examine some approaches of the Saga pattern, Request/Response pattern, and MassTransit Automatonymous library with samples in detail.

Approaches to Implementing Saga Pattern

Some approaches allow us to implement the Saga pattern, and depending on the necessity, one can be preferred. Choreography and Orchestration-based Saga approaches will be discussed in this part of the article.

1-) Choreography-based Saga

In this approach, all services that are a part of our business process trigger the next by publishing the event whenever they complete their local transaction. Thus, there is no required central point like orchestrator/state machine. On the other hand, the issue with this approach is that so much point-to-point communication about microservices leads to complexity and circular dependency. Therefore, this approach can be used if there are less than 3–4 services in the business process and we do not need any central point to maintain some state about the process.

2-) Orchestration-based Saga

In this approach, there is a central point called Saga State Machine. It manages all transactions between services and leads to which operation to take based on events. During the long-running business process, if a failure happens, State Machine will publish compensation events for undoing what happened before the failure step. Also, State Machine persists data about the state to control the flow and execute the process according to the current state.

The Request / Response Pattern

First, it is essential to understand how ordinary asynchronous messaging works. For instance, when we publish an event as “Fire and Forget,” the publisher does not know whether the event is consumed. In this case, the Request/Response pattern allows us to publish an event, wait until the event is consumed, and get a response without using Connection Pooling or WebSocket.

Secondly, this pattern allows us to decouple our microservices. Typically, whenever we call another service during a business process, we make a synchronous HTTP request to get the needed data to perform the subsequent steps we have. With this pattern, we are not calling services by an HTTP request. We publish the event and wait until the event is consumed and get a response for using it for the subsequent steps. Hence, it prevents services from calling each other with a synchronous HTTP request.

The MassTransit Automatonymous State Machine

MassTransit has a state machine library called “Automatonymous,” which includes states, events, and behaviors. It has a class named “MassTransitStateMachine” to inherit for defining state machine and a “SagaStateMachineInstance” interface to define the object corresponding to the current state and details about the related process. A Saga instance is an object that can transition from one state to the other.

We finalized the theory part of the subject, so let us look at how it seems in the following code snippets. This sample project was developed with .NET Core 6.0, where I used RabbitMQ as the message broker and MongoDB to persist the state machine data.

Solution structure of the project

In the sample project that we are going to cover, these are the steps for the process:

OrderInitialization , CheckProductStock, TakePayment, CreateOrder

Let’s start with creating our StateMachine and StateMachineInstance classes.

Implementing the “ISagaVersion” interface is required to persist the data in MongoDB.

We define event properties on the state machine class. Each event also has its compensation/fault event that undoes what was executed on that event.

In this phase, we also define the state properties on the state machine class related to the events we created.

And then, we should declare the property to maintain the instance’s state.

After defining properties on the state machine class, we need to introduce these events to the state machine.

Right now, we should build some flow according to our business process.

Let’s build some compensation/fault flow to undo what we did until the failure point.

Implementation of OrderProcessInitializerEvent

Program.cs configuration of State Machine

Program.cs Configuration of Sample API. The AddConsumer method to consume a published event. The AddRequestClient method is for the Request/Response pattern to publish, consume the event, and then wait for a response.

IPublishEndpoint is for only publishing events. However, IRequestClient is to utilize the Request/Response pattern.

Sample endpoint for trigger initialized event. If IsRequestResponsePattern is false, then it only publishes an event. However, if it is true, then the Request/Response pattern is implemented to publish the event and wait for a response.

ConsumerBase is created that implements the IConsumer interface from MassTransit to make a “Template Method Design Pattern.” When an event consumer gets triggered first, we wrap the code with a try-catch statement. If any exception occurs, the context automatically publishes its fault event to inform the state machine of a failure.

This code snippet describes a sample consumer — RespondAsync methods stands for implementation of Request/Response pattern. When the consumer executes some logic, then with RespondAsync, we return an object to the publisher. Thus, if we do not use RespondAsync, we cannot use this pattern.

In conclusion, this article clarifies the Saga pattern, its contributions, and its two approaches (Choreography-Orchestration-based Saga pattern). Also, this article covers Async Messaging with Saga State machine, MassTransit Automatonymous library, and Request/ Response pattern by visualizing it with code snippets. The Saga pattern enables data consistency by systematically processing numerous transactions on different and independent services. To this end, this pattern embraces two approaches to achieve its purpose, and choosing the optimum one among the relevant approaches depends on the current case.

The sample project that I just visualized on some pieces of the article can be found at:

Please do not hesitate to contact me if you have any questions.

Peace | Love | Code

--

--