Transactional OutboxをAWSで実現する

# 概要

* 本記事の目的は、マイクロサービスアーキテクチャでよく使われるパターン、[Transactional Outbox](https://microservices.io/patterns/data/transactional-outbox.html)についての説明とAWSで実現する方法をお届けすることとしています。
* Transactional Outboxを説明する中でマイクロサービスアーキテクチャについてや、Sagaパターンについてを詳細に述べることは致しません。

## Transactional Outbox

### Sagaパターン
* マイクロサービスで複数のリソースの結果整合性を利用する方法として、Sagaパターンと呼ばれるものがあります。
* Sagaパターンというのは、複数にまたがるサービスごとでトランザクションを分解し、それぞれが独立で処理するようなアーキテクチャパターンです。
* Sagaパターンによりトランザクションをサービスごとに分解することの何が嬉しいのか。
=> それは１トランザクションあたりにかかる時間を短縮し、それによって複数行、テーブル全体を長時間ロックすることなく処理を実行できることにあります。
* 下記の図で示しているのが、Sagaパターンの一例です。
* 緑色のヘキサゴンで示したものが、複数のサービスを表しています。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563635/2f57fcc0-a100-03a8-964d-6b12d7dcc7d4.png)

### Sagaパターンの複雑性

* Sagaパターンには実装の複雑さがあります。
* どういうことか？ というと、Sagaパターンをロールバックさせようとした時に単純なロールバックはできないためです。
* なぜならば、複数のトランザクションが関与しているので、途中で障害が発生した場合、障害が発生した以前の状態に回復させようとロールバックすると既に別のコミットが発生している可能性があります。
* そのため、Sagaパターンでは**補償トランザクション**と呼ばれる方法を適用します。
* 補償トランザクションとは、一連のトランザクションで実行した操作の逆向きにした取り消し操作によってロールバックを行います。

* 下記の図で示しているのが、各ローカルトランザクションとその操作で実行する責務を持つマイクロサービス例で、トランザクション実行中エラーが起き、補償トランザクションが実行された時を示しています。

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563635/81f7aa76-86bf-19a5-c54b-434f33a7d25d.png)


* このSagaパターンを実装する時にも２つほどパターンがありますが、ここでは詳細に紹介致しません。
* もし気になる方がいれば、下記の記事をご参照ください。

https://qiita.com/yoshii0110/items/4ae10eb071565cb90b37

---

* さて、ではSagaパターンを実際に構築しましょう。となったときのパターンとして、Choreography (コレオグラフィ)を選択したとしましょう。
* コレオグラフィのSagaパターンでは、メッセージブローカを使用し、各のマイクロサービスがイベントを交換します。
* コレオグラフィを使用すると、他のサービスでローカルトランザクションをトリガーするイベントが、各ローカルトランザクションによって発行されます。
* 下記の図で示しているのが、コレオグラフィの一例です。

![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563635/60f9fd27-406a-76d2-c9f8-cbfcbf5fa465.png)

* もし、この構成の時にメッセージブローカに流した変更とその更新によりデータベースへも更新が走った場合、それぞれに書き込みを実施する**２重書き込みを実施するパターン**と**分散トランザクションにするパターン**を使用しないといけません。
  * **２重書き込みの問題点:** 二重書き込みではどちらかの書き込みは成功し、もう一方の書き込みは失敗するような場合がありますし、障害が発生するタイミングなどでデータの不整合が生じる可能性もあります。
  * **分散トランザクションの問題点:** DBとメッセージングシステムで分散トランザクションがサポートしている必要があります。そのため、利用できる技術が限定されてしまいます。

**ではどうするのか？**
=> そこで登場するのが、[Transactional Outbox](https://microservices.io/patterns/data/transactional-outbox.html)です。

### Transactional Outbox

* Transactional Outboxとは、データベースとメッセージングシステムへの分散トランザクションを用いた書き込みはせず、データベースに追加したOutboxテーブルにメッセージの内容を書き込みます。
* この時、Outboxテーブルへのメッセージの書き込みにはローカルトランザクションを用いることができます。
* 下記に示したのがTransactional Outboxの一例になります。
* こちらの例では、何かしらの注文を受け取るサービスがあった時に、そのサービスからの変更をOrderテーブルに書き込み、その際にOutboxテーブルにも変更した内容を入れています。
* この時にはデータベースがサポートするローカルトランザクションを使用することができますよね。

![スクリーンショット 2022-07-01 0.10.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563635/a07e2dc5-db5e-8236-8d9c-c80b70b4b554.png)

[画像引用元](https://microservices.io/patterns/data/transactional-outbox.html)

* 次にその後の処理を追ってみましょう。
* Outboxテーブルへ書き込まれたメッセージは、MessageRelayを経由し、MessageBrokerへ送信されていることがわかります。こうすることにより、DBへの書き込みとメッセージングシステムへのメッセージの書き込みをアトミックに実行できます。

* **MessageRelay:** Outboxテーブルにメッセージが書き込まれ更新されると、そのメッセージを読み取り、後続のメッセージブローカへ送信する役割を持ちます。
* ちなみにこのMessageRelayにもパターンがあります。
  * **Mpolling publisher パターン**
  * **transaction log tailing パターン**

これらのパターンの違いを知りたい場合は、下記の記事を見ることをオススメいたします。

https://pradeepl.com/blog/transactional-outbox-pattern/

## Transactional OutboxをAWSで実現する

* いよいよ本題です。
* Transactional Outboxについての説明を上記で行いましたが、いざこれをどうやって実現しようとなると思います。
* 今回は、AWSの中でこのTransactional Outboxを実現する方法についてを説明していきましょう。
* AWSで実現するときに一番簡単にできるのが[DynamoDBストリーム](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Streams.html)を使用することです。
  * DynamoDBストリームとは、DynamoDBのテーブルに更新があった際に、その変更を暗号化してログに保存しておくことができます。
  * また、DynamoDBストリームでは更新された内容をほぼリアルタイムで書き込みます。
  * 下記の図は、DynamoDBのテーブルにOrderというサービスから更新を受けた時に、ストリームで生成されるイベントの流れを示しています。

![スクリーンショット 2022-07-01 0.43.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563635/d5e5f94e-12c7-3fb4-ec91-f360ed70b3df.png)

* また、DynamoDBストリームを使用することで後続の処理(Orderサービスによって受けた変更を利用し、処理するサービス)の実装を簡単にすることができます。
* 例えば、Orderサービスによって更新された内容を別のサービスが使用したいとしましょう(加工し)
* その場合は、以下のような構成もとることができます。
* [DynamoDBストリームでは、Lambdaとの統合機能](https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Streams.Lambda.html)があるので、そちらを使用します。
* あとはSQSに送信し、他のサービスがその内容を取得するといった形ですね。

![スクリーンショット 2022-07-01 0.53.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/563635/454fadc5-ea3c-266e-e4ca-63e9e47b0197.png)


# まとめ

* いかがだったでしょうか。
* 今回は、Transactional OutboxのついてとそれをAWSで実現するにはという内容を記載させていただきました。

# 参考

https://microservices.io/patterns/data/transactional-outbox.html

https://pradeepl.com/blog/transactional-outbox-pattern/

https://highwide.hatenablog.com/entry/2020/09/20/181500

https://www.infoq.com/jp/articles/saga-orchestration-outbox/

https://techblog.zozo.com/entry/implementation-of-cqrs-using-outbox-and-cdc-with-dynamodb

https://medium.com/ssense-tech/implementing-a-transactional-outbox-pattern-with-dynamodb-streams-to-avoid-2-phase-commits-ed0f91e69e9

https://golangexample.com/a-demonstration-of-the-transactional-outbox-messaging-pattern-log-trailing-with-amazon-dynamodb-streams-written-in-go/

https://thorben-janssen.com/dual-writes/