Skip to content

Commit

Permalink
feat: add saga pattern (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonaslagoni authored Oct 10, 2024
1 parent c4c78c7 commit 7ed9bf2
Show file tree
Hide file tree
Showing 5 changed files with 327 additions and 3 deletions.
Binary file added patterns/assets/images/event-source.webp
Binary file not shown.
Binary file added patterns/assets/images/saga.webp
Binary file not shown.
2 changes: 2 additions & 0 deletions patterns/event-source.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ In a nutshell it is the principle of having a stream of events, each describing

Event Source is in its nature append-only, meaning that events once entered the stream cannot change, only be appended. If the data is not append-only, you allow to modify, delete, or reorder those events it would make it an Mutable Event Log.

![event sourcing banner, created by DALL-E](./assets/images/event-source.webp)

NOTICE: Event Source and [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource), is two different things, the first is a design pattern, the second is a the interface for [server-sent-events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)

In AsyncAPI there are no way to describe explicitly you are using event source, except through meta data such as `description` and `summary`. However, one might look like the following:
Expand Down
10 changes: 7 additions & 3 deletions patterns/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,31 @@ title: Introduction
It includes a range of patterns used in EDA with a specific focus on how to design it with [AsyncAPI](https://www.asyncapi.com/).

## Roadmap
Simple roadmap for patterns to document;

- [X] [Add ECST pattern](./event-carried-state-transfer.md)
- [X] [Add CQRS pattern](./command-query-responsibility-segregation.md)
- [X] Basic doc rendering
- [X] Github pages
- [X] [Event Sourcing](./event-source.md)
- [ ] Change Data Capture (CDC)
- [X] [Change Data Capture (CDC)](./cdc.md)

Communication Patterns

- [ ] Request-Reply
- [ ] point-to-point
- [ ] event streaming
- [ ] Publish-Subscribe pattern

Consumer Scalability / Patterns

- [ ] consumer groups
- [ ] partitioning
- [ ] Exclusive Consumer (HA)

Error Handling Patterns
- [ ] dead letter queue

- [X] [dead letter queue](./dead-letter-queue.md)
- [ ] discard
- [ ] pause and retry
- [ ] saga
- [X] [saga](./saga.md)
318 changes: 318 additions & 0 deletions patterns/saga.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
---
title: Saga pattern
description: How you orchestrate transactions in a micro service environment
---

# Saga pattern
When placing an order, there are likely multiple services in the backend that needs to "sign-off" that the purchase went through. For example, a step in the process could be to check that the customer has not reached it's credit limit, and if they have, the transaction needs to be rejected in some fashion.

How those long-running transaction is coordinated across multiple microservices, ensuring data consistency and eventual completion is possible through the Saga pattern.

There are two saga types, choreograph and orchestration based. The main difference between the two is how the transaction is coordinated and who is responsible for what.

![Saga banner, created by DALL-E](./assets/images/saga.webp)

## Choreograph based
Is the decentralized coordination pattern. Each service involved in the transaction is responsible for listening and reacting to events from other services.

For example; The payment service performs a payment and emits a "Payment Successful" event, which is consumed by the shipping service to trigger shipping. There are no central service that asks the services to perform anything.

Notice how the shipping service is receiving when payment is successful on it's own without a single service handling the transaction.

<table>
<tr>
<th> Payment service </th>
<th> Shipping service </th>
</tr>
<tr>
<td>

```json
{
"asyncapi": "3.0.0",
"info": {
"title": "Payment service",
"version": "1.0.0"
},
"channels": {
"PaymentSuccess": {
"description": "Payment was successfully made",
"address": "payment.success",
"messages": {
"PaymentSuccess": {
...
}
}
}
},
"operations": {
"PaymentMade": {
"action": "send",
"channel": {
"$ref": "#/channels/PaymentSuccess"
}
}
}
}
```
</td>

<td>

```json
{
"asyncapi": "3.0.0",
"info": {
"title": "Shipping service",
"version": "1.0.0"
},
"channels": {
"PaymentSuccess": {
"description": "Payment was successfully made",
"address": "payment.success",
"messages": {
"PaymentSuccess": {
...
}
}
},
"OrderShipped": {
"description": "Order was successfully shipped",
"address": "order.sent",
"messages": {
"OrderShipped": {
...
}
}
}
},
"operations": {
"PaymentMade": {
"action": "receive",
"channel": {
"$ref": "#/channels/PaymentSuccess"
}
},
"OrderShipped": {
"action": "send",
"channel": {
"$ref": "#/channels/OrderShipped"
}
}
}
}
```

</td>
</tr>
</table>

## Orchestration based

A Orchestration based Saga is a single service or part of another that ensures that the transaction runs as expected and calls the corresponding services that are part of the transaction.

For example; The orchestrator calls when an order is received calls the payment service, and finally the shipping service, controlling the coordination of the transactions.

Notice how the Orchestrator are interacting with payment and shipping service and receive and send to both, orchestrating when and what happens.

<table>
<tr>
<th> Orchestrator </th>
<th> Payment service </th>
<th> Shipping service </th>
</tr>
<tr>
<td>

```json
{
"asyncapi": "3.0.0",
"info": {
"title": "Orchestrator",
"version": "1.0.0"
},
"channels": {
"PayForOrder": {
"description": "Make the payment for the order",
"address": "payment.pay",
"messages": {
"PaymentPay": {
...
}
}
},
"PaymentSuccess": {
"description": "Payment was successfully made",
"address": "payment.success",
"messages": {
"PaymentSuccess": {
...
}
}
},
"SendOrder": {
"description": "Send an order",
"address": "order.send",
"messages": {
"SendOrder": {
...
}
}
},
"OrderShipped": {
"description": "Order was successfully shipped",
"address": "order.sent",
"messages": {
"OrderShipped": {
...
}
}
}
},
"operations": {
"PayForOrder": {
"action": "send",
"channel": {
"$ref": "#/channels/PayForOrder"
}
},
"PaymentMade": {
"action": "receive",
"channel": {
"$ref": "#/channels/PaymentSuccess"
}
},
"SendOrder": {
"action": "send",
"channel": {
"$ref": "#/channels/SendOrder"
}
},
"OrderShipped": {
"action": "receive",
"channel": {
"$ref": "#/channels/OrderShipped"
}
}
}
}
```
</td>

<td>

```json
{
"asyncapi": "3.0.0",
"info": {
"title": "Payment service",
"version": "1.0.0"
},
"channels": {
"PayForOrder": {
"description": "Make the payment for the order",
"address": "payment.pay",
"messages": {
"PaymentPay": {
...
}
}
},
"PaymentSuccess": {
"description": "Payment was successfully made",
"address": "payment.success",
"messages": {
"PaymentSuccess": {
...
}
}
}
},
"operations": {
"PayForOrder": {
"action": "receive",
"channel": {
"$ref": "#/channels/PayForOrder"
}
},
"PaymentMade": {
"action": "send",
"channel": {
"$ref": "#/channels/PaymentSuccess"
}
}
}
}
```
</td>

<td>

```json
{
"asyncapi": "3.0.0",
"info": {
"title": "Shipping service",
"version": "1.0.0"
},
"channels": {
"SendOrder": {
"description": "Send an order",
"address": "order.send",
"messages": {
"SendOrder": {
...
}
}
},
"OrderShipped": {
"description": "Order was successfully shipped",
"address": "order.sent",
"messages": {
"OrderShipped": {
...
}
}
}
},
"operations": {
"SendOrder": {
"action": "receive",
"channel": {
"$ref": "#/channels/SendOrder"
}
},
"OrderShipped": {
"action": "send",
"channel": {
"$ref": "#/channels/OrderShipped"
}
}
}
}
```

</td>
</tr>
</table>

## Final remarks

The main differences can be seen below;

| Aspect | Choreography | Orchestration |
|----------|:-------------:|:------:|
**Control** | Decentralized, self-coordinated|Centralized, orchestrator-controlled
**Communication** | Event-driven (publish/subscribe) | Direct service invocation
**Coupling** | Loosely coupled | Loosely coupled, but tightly coupled to the orchestrator
**Flow Complexity** | Complex with multiple services | Centralized and easier to trace
**Failure Handling** | Compensating actions handled individually by services | Orchestrator manages compensations
**Flexibility** | More flexible and scalable in smaller, simpler use cases | Easier to manage in complex workflows

For better specifying the exact fail scenarios and workflows, one might take a look at [Arazzo](https://spec.openapis.org/arazzo/latest.html).

# Resources

- https://microservices.io/patterns/data/saga.html
- https://medium.com/cloud-native-daily/microservices-patterns-part-04-saga-pattern-a7f85d8d4aa3
- https://www.geeksforgeeks.org/saga-design-pattern/

0 comments on commit 7ed9bf2

Please sign in to comment.