🗂

GoのライブラリのコードリーディングがSagaパターンを学ぶきっかけを与えてくれた

2023/01/08に公開

※ この記事ではSagaパターンの体系的な説明は行なっていません!

はじまり

Go のライブラリである samber/lo を眺めていたところ、ある文言が目に止まる。

Transaction
Implements a Saga pattern.

Saga pattern ?聞いたことあるようなないような。
おそらくトランザクション関連のデザインパターンか何かであろう。
聞き覚えがあるかも怪しいワードであるが、実装を読めばこのデザインパターン?アーキテクチャ?が何であるかがきっと少しは分かるであろう。
業務で使ったこともないし今後使うかも不明だが、知っていたら格好良くてお洒落な気がする。
そのような愚かなモチベーションでソースコードを読み始めた。

コードリーディング

件の実装は retry.go というファイルの関数 NewTransaction 辺りが該当するようだ。
処理の再試行、後退の制御等を司るような実装がであることが予想される。
早速中身を深掘りする。

関数 NewTransaction を呼び出すと、構造体 Transaction が返る。
Genericな関数、構造体となっており関数呼び出し時に型パラメータを渡す必要がある。
この段階では何をする関数、構造体なのかはさっぱり予測がついていない。私は弱い。

https://github.com/samber/lo/blob/master/retry.go#L163-L167

構造体 Transaction の定義を確認する。
フィールド steps に実行したい処理を追加していき、複数の処理が最終的にトランザクション処理となるような気がする。
追加、削除はメソッド経由でのみ許可したい理由があってPrivateなフィールドにしているのであろう。

https://github.com/samber/lo/blob/master/retry.go#L170-L172

構造体 Transaction のフィールド steps の型で使用されていた構造体 transactionStep を見てみる。
フィールド exec は実行したい処理、onRollbackexec の処理を後退する処理と考えられる。
やりたいことが分かってきた気がする。

https://github.com/samber/lo/blob/master/retry.go#L157-L160

ここからは構造体 Transaction に生えているメソッドを読んでいき、ここまでで予想したことの答え合わせを行う。

メソッド Then は実行したい処理と後退処理を引数で受け取り、それぞれを構造体 transactionStep のフィールドにセットし、レシーバの構造体 Transaction の フィールド steps に追加する。
これはトランザクション処理の下準備だ。
戻り値が更新後の構造体 Transaction であるので、メソッドチェーンが可能!(Go らしくないと言われることもあるが、メソッドチェーンが好物)

https://github.com/samber/lo/blob/master/retry.go#L175-L182

これが最後だ、メソッド Process
レシーバの構造体 Transaction の フィールド steps の要素である関数を昇順に実行していく。
途中でエラーが発生すれば、当該処理から降順に後退処理を実行していく。
また、メソッド Then のリーディング時に雑に読み飛ばしていたが、実行したい処理と後退処理はどちらもジェネリックな型の引数を受け取り、同じ型を返している。
後続の処理に値をバケツリレーできるようになっているのだ。イケている。

https://github.com/samber/lo/blob/master/retry.go#L185-L208

おわり

素敵なコードだった。読んでいて普通に楽しかった。
コードの意味はちゃんと分かったつもりであるが、あくまでSagaパターンの一実装を知っただけであるし、もう少し上位のレイヤーで用いられるようなデザインである気がする。(マイクロサービスまたぎのトランザクションとか?)
今誰かに「Sagaパターンって何?」と聞かれてもモゴモゴしてしまうレベルではあるが、Sagaパターンっていう用語が出てきた時にふんわりとイメージはつくレベルには到達できたと思う。
もう少し体系的に知りたい気持ちが出てきたので、記事漁りでも始めます。

内容に誤りやわかりにくい点ありましたら、お気軽にコメントして頂けると幸いでございます。

Discussion