マイクロサービスアーキテクチャにおけるデータベース再び
以前のブログで、マイクロサービスにおけるデータベースの持ち方と、その場合のデータの整合性の取り方の問題について解説しました。
今回も、マイクロサービスアーキテクチャにおけるデータベースの持ち方に関するデザインパターンの特徴について説明し、その後にこのデザインパターンにおいて起きる起きるもう一つの問題、トランザクションをどうやって実装するかという点について解説したいと思います。
Database per Serviceパターンにおけるトランザクションの難しさ
マイクロサービスアーキテクチャにおいてデータベースをどのように持つか?という問いに対する答えには、前回説明したとおりDatabase per Serviceというデザインパターンがありました。
このデータベースのデザインパターンの特徴は、データベースが必要なサービスごとにそのサービスとだけやりとりするデータベースを用意し、ほかのサービスとはやりとりしないということでした。
これによって、サービスとデータベースが1セットになり、ほかのサービスに依存しないことで、マイクロサービスアーキテクチャの特徴でありメリットでもあるサービス間の疎結合を実現していました。
さて、この場合において、複数のサービスが持つデータベースを同時に更新しなければならなくなった場合、どうすればいいでしょうか?
RDBにおいては、ほとんどの場合トランザクションの機能が実装されており、複数のテーブルにまたがる処理をしていてエラーが発生した場合、その処理を始める前の状態に自動的に戻してくれます。
NoSQLにおいても、はじめはNoSQLの特徴としてトランザクションを実装しないことによって軽量な動作を実現しているという点があったくらいでしたが、最近はいくつかのDBMSにおいてはトランザクション機能が実装され始めており、データベースを作るうえではやはり重要度の高い機能と位置付けられているようです。
さて、Database per Serviceパターンの場合はどうでしょうか?
仮にそれぞれのサービスが持つデータベースにそれぞれトランザクションの機能があったとしても、ほかのデータベースに対しては適用できません。
トランザクションで処理を纏められるのは自分のデータベースの中のテーブルに対してのみで、ほかのデータベースとの間にまたがる処理のトランザクションについては、別の方法で実装する必要があります。
Sagaパターンによる解決策
複数のサービスのデータベースにまたがる処理を実装するためのデザインパターンとしてSagaパターンというものがあります。
これは、複数のデータベースにまたがる処理をSagaとして一つにまとめ、メッセージを通して一つ一つのサービスに対する処理を指示したり、その結果を受け取ったりすることでトランザクションを実現する方法です。
そして、もしも処理のいずれかが失敗した場合、「補償トランザクション」という、ロールバック用のトランザクションを行ってロールバックを行います。
このSagaの実装の方法については二つのアイデアがあります。
ChoreographyベースのSaga
Choreographyとは、日本語に訳すと「振付け」になります。
Sagaのうちの一つの処理が終わると「こっちではこの作業が終わったから次はこの作業をお願いね」といったようなメッセージを次の処理を行うサービスに対して流します。
そこでの処理が終わればさらに次のサービスに同様のメッセージを送る・・・といった流れを繰り返すのが正常系です。
また、もしもどこかのサービスで処理の失敗が起これば、そこからその前の処理を行ったサービスに対して、「こっちで処理に失敗しちゃったからやった処理を元に戻して!」というメッセージを流します。
そして、それを受け取ったサービスは行った処理を元に戻す処理を行ってから、さらに前に処理を行ったサービスに対して同様のメッセージを流します。
このようにして、ロールバックの処理を実現します。
OrchestrationベースのSaga
このパターンにおいては、トランザクションの起点になるサービスに、そのトランザクションのSagaとなる部分をもち、ほかのサービスへの指示やそのサービスでの結果の受け取りを全て担当します。
つまり、Sagaに関わる全てのサービスはこのSagaと1対1の関係を持ち、処理を行います。
二つのパターンのどちらがいいの?
Sagaの実装方法Webアプリケーションを2パターン観てきましたが、そうするとこれら二つのどちらを使って実装するかということを考えなければならなくなります。
ちなみに、私はOrchestrationベース推しです。
というのも、こちらのパターンではトランザクションの中身を全てまとめたSagaの部分が1つのサービスの中にあり、それによってトランザクションに関わるサービスが全部一目瞭然になるからです。
Choreographyベースの場合だと、トランザクションの流れを追うにはまずここで処理が起きて次はこのサービスにメッセージが送られてそこからさらにこのサービスにメッセージが送られて・・・という流れを全部見なければならなくなります。
仕様としてどこかにまとめておくこともできますが、その手間を考えるとコードを見るだけでその周りを把握できることは強みのように感じます。
そのほかの解決策
余りに多くの場合に複数サービス間でのトランザクションを実現しなければならない場合は、そもそもの設計が間違っていることを考えなければならないかもしれないです。
マイクロサービスアーキテクチャにおけるサービスの分割の仕方は、ドメイン駆動設計の境界コンテキストパターン似たものですが、適切にサービス分割がされていないと、実際にはほかのサービスにまたがる処理がいくつもあったという場合もあります。
こういった場合、それらのサービスについてはやはり同じ一つのサービスだったとして一度同じサービスにまとめることが有効だったりします。
同じサービスにすれば分散したデータベースを一つにまとめてもいいので、それによってその中でのトランザクションを実現できます。
まとめ
本稿では、以前のブログに引き続いてマイクロサービスアーキテクチャにおけるデータベースの持ち方とその問題について解説しました。
Database per serviceパターンにおいてはトランザクション処理を行うのは自分で実装しなければならないこと、そしてその実装のためのデザインパターンとしてSagaパターンがあることを説明しました。
マイクロサービスアーキテクチャは新しく、魅力的な技術です。
それだけに、問題点もいくつかあり、その解決法を共有することはとても有意義なことだと考えてこのような記事を書きました。
もう少し分かりやすく書けないかというのはいつも考えていますが、この記事が理解の助けになれば幸いです。