オブジェクトの広場はオージス総研グループのエンジニアによる技術発表サイトです

コンテナ・マイクロサービス

マイクロサービスオーケストレーションを助けるZeebe

金融ソリューション第二部
熊木 亮人
2021年3月18日

本稿ではマイクロサービスアーキテクチャでの利用が可能なワークフローエンジンのZeebeを紹介します。このZeebeを利用することによって、開発者はマイクロサービスにおけるサービス間の連携をグラフィカルに記述することができ、またアプリケーションの実行状態を監視することも可能になります。

はじめに

最近のシステム開発では、マイクロサービスアーキテクチャと呼ばれるアプリケーションを疎結合なサービスで構築するアーキテクチャを検討することが増えてきています。

マイクロサービスでは複数のサービスを組み合わせてアプリケーションが実現されるため、アプリケーションが複数のサービスを横断してどのように組み立てられているか、またそれぞれのサービスの状態をどのように監視していくかは重要なポイントとなっていくでしょう。

実際のマイクロサービスの現場でも、複数のチームをまたいで一連の取引を組み立てる場合には、全体でどのようなフローになっているかを共有していく作業は重要となります。ですが、このような処理フローはコードベースで管理されることが多く、開発が進むにつれて設計書は陳腐化し、全体のフローの把握が難しくなることも少なくありません。

そこで、本稿ではマイクロサービスアーキテクチャでの利用が可能なワークフローエンジンのZeebeを紹介します。このZeebeを利用することによって、開発者はマイクロサービスにおけるサービス間の連携をグラフィカルに記述することができ、またアプリケーションの実行状態を監視することも可能になります。それでは、Zeebeを利用してどのようにマイクロサービスにおけるワークフローを実現できるのかをステップごとに見ていきたいと思います。

マイクロサービスアーキテクチャの概要

マイクロサービスアーキテクチャとは、アプリケーションを複数の疎結合なサービスで構成するアーキテクチャスタイルのことを指します。それぞれのサービスは個別にデプロイ、スケーリング、技術の選択が可能なコンポーネントとして設計されており、実行は異なるプロセスで行われることになるため、サービス間の連携はREST APIや非同期通信などのプロセス間通信で実装されます。

マイクロサービス_概要図.png

大きな単一のモノリシックアーキテクチャでアプリケーションを構築することに比べて、マイクロサービスアーキテクチャで構築されたアプリケーションはサービスを個別にデプロイすることが可能となり、ビジネスの変化にあわせて素早く機能を追加、変更することが可能となります。また、サービスのカプセル化を保つことが容易になり、変更による影響の局所化やサービス間の疎結合を維持することが容易になります。

このように、マイクロサービスアーキテクチャを導入することでビジネスが大きなメリットを得ることになりますが、マイクロサービスアーキテクチャで構築されたアプリケーションは複数のサービスを横断して実現されるため、1つの取引を完了させるには関連するすべてのサービスが成功しなければなりません。そのため、処理フローのモデル化や状態の可視化など、サービスを横断してアプリケーションを構築、管理、監視できる仕組みが必要となってきます。

Zeebe

Zeebeはグローバルに拠点を構えるBPM(Business Process Management)専門のコンサルタント会社であるCamundaによって開発、管理されているマイクロサービス向けのワークフローエンジンになります。

Zeebeではアプリケーションにおけるサービスの実行順序、分岐、データの制御をすべてBPMNで記述できます。BPMNを利用することによって非技術者でも理解しやすい形でアプリケーションを表現することが可能になり、アプリケーションの設計を技術者と非技術者が共同で行うことができます。また、Zeebeは水平スケールすることで1秒当たり100万件を超えるプロセスインスタンスの実行が可能なため、同様のワークフローサービスと比較しても高いスループットを実現できます。

ライセンス

ライセンスはクリエィティブコモンズ(表示)ライセンスに相当するZeebe Community License v1.0が一部のソースコードを除いて適用されており、通常の用途では問題なく利用できますが、クラウドプロバイダーなどによる商用ワークフローサービスの提供には利用することができません。

※ 以下のソースコードはApache License 2.0が適用されています

アーキテクチャ

Zeebeはクライアント、ジョブワーカー、ゲートウェイ、ブローカー、エクスポーターというコンポーネントによって構成されます。
(これらのコンポーネントの概要については、公式Docをご参照ください。)

zeebe-architecture.png
  Zeebeのアーキテクチャ(引用:Camunda Cloud Docs Webサイト

Zeebeでは業務処理を実現するために行われる一連のサービス呼び出しはワークフローと呼ばれ、ワークフロー内におけるサービスの呼び出しの順序、状態の管理はオーケストレーションという方式で実現されています。オーケストレーションではサービスの呼び出しとサービス間の結合はオーケストレーターと呼ばれる役割によって行われますが、Zeebeではこのオーケストレーターはブローカーとゲートウェイで構成されるZeebeクラスターによって実現されます。

Zeebe_オーケストレーション.png

また、Zeebeクラスターは複数のブローカーで構成することができ、ブローカーを増やすことで水平スケーリングが可能となるアーキテクチャが採用されています。加えてブローカー間では障害に備えてデータが複製されるため、高いスケーラビリティと耐障害性を備えていることも特徴的でしょう。

開発で利用可能なツール

ZeebeではBPMN2.0でのワークフローの記述が可能になるソフトウェアや、開発環境でのワークフローの監視が可能になるソフトウェアなど、Zeebeを利用したアプリ開発で有用なツールやライブラリがAwesome Zeebeで紹介されています。ここでは今回の記事で利用するものに絞って紹介します。

Zeebe Simple Monitor

Zeebe Simple Monitorは開発向けのZeebeモニタリングアプリケーションとなります。Apache License2.0で作成されているOSSであり、ワークフローのデプロイや手動でのワークフローインスタンスの実行、および状態の監視を行うことが可能です。

GitHub - zeebe-io/zeebe-simple-monitor

Zeebe Modeler

Zeebe ModelerはBPMN2.0でワークフローを記述することができるMITライセンスで作成されているOSSモデリングツールとなります。GUIでの操作となるため、BPMNに詳しくない開発者でも直感的にワークフローを記述することが可能となります。

GitHub - zeebe-io/zeebe-modeler

Zeebeを利用したワークフローの実行

では、Zeebeを利用してワークフローを構築、実行していきましょう。まずはBPMNで記述したワークフローをZeebeクラスターへ登録し、サービスをZeebeクライアントとして実行する流れを説明していきたいと思います。

環境準備

ワークフローの構築を始める前に、Zeebeクラスターとワークフローのデプロイを行う環境を作成する必要があります。Zeebeクラスターに関しては、ローカルで簡単に試行環境を作成するためのdocker-composeファイルがZeebeの公式リポジトリで提供されているので、今回はそちらを利用して仮の環境を作成していきましょう。

  • Zeebeクラスターの環境の作成
    docker-composeを利用してZeebeクラスター、Zeebe Simple Monitorを開始します。
  git clone https://github.com/zeebe-io/zeebe-docker-compose.git
  cd zeebe-docker-compose/simple-monitor
  docker-compose up -d

上記のコマンドを実行することで、Zeebe BrokerとZeebe Simple Monitorがローカル環境にデプロイされます。

開発環境.png

ワークフローをZeebeクラスターへデプロイ

今回のワークフロー作成では、Zeebeの公式サイトから提供されているワークフローを利用しZeebeクラスターへデプロイをしていきましょう。

  • order-process.bpmnファイルを取得
  curl -OL https://docs.camunda.io/assets/files/order-process-39e893672e53060d69ca6789982c15e3.bpmn
  • BPMNファイルのデプロイ

order-process.bpmnファイルを取得したら、任意のブラウザからhttp://localhost:8082にアクセスしてZeebeシンプルモニターの画面を立ち上げます。

zeebe_simple_monitor_index.png

「New Deployment」のボタンを押下して、先ほど取得したorder-process.bpmnを選択し、 「Deploy」でZeebeクラスターへワークフローをデプロイします。

workflow_deploy.png

workflow_deploy_finished.png

“Success: New deployment created.(Refresh)"が表示されたことを確認し、画面を更新します。すると先ほどのorder-process.bpmnで記述されたワークフローがZeebeクラスターへデプロイされたことが確認できました。

workflow.png

以上でBPMNファイルを用いてZeebeクラスターへワークフローをデプロイする作業は完了です。デプロイ作業がGUIで可能なこともあり、とても簡単にデプロイできたのではないでしょうか。

Zeebeクラスターにサービスを接続

先ほどの手順でワークフローのデプロイが完了したので、次にワークフローのCollect Moneyジョブにサービスを割り当てていきます。

Collect Moneyジョブはpayment-serviceというタスク定義でサービスを待機しているので、payment-serviceという名前でサービスを接続していきましょう。今回はJavaでサービスを作成するため、以下のクラスをコンパイル・実行してください。実行には zeebe-client-javaのライブラリが必要となるため、各自の環境に合わせて依存関係の追加が必要となります。

import io.zeebe.client.ZeebeClient;
import io.zeebe.client.api.worker.JobWorker;

public class PaymentService {

    public static void main(final String[] args) throws InterruptedException {
        final ZeebeClient client = ZeebeClient.newClientBuilder().gatewayAddress("localhost:26500")
                .usePlaintext().build();
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            client.close();
            System.out.println("shutdown..");
        }));

        final JobWorker jobWorker = client.newWorker().jobType("payment-service").handler((jobClient, job) -> {
            System.out.println("accepted a job.");
            jobClient.newCompleteCommand(job.getKey()).send().join();
        }).open();
        System.out.println("Service start.");
        while(true){
            Thread.sleep(1000);
        }
    }
}

このクラスを実行することで、ワークフローのジョブにサービスを割り当てることができました。では、次のステップでは実際にワークフローインスタンスを実行してジョブに割り当てたサービスが実行されることを確認します。

Zeebeクラスターにワークフローインスタンスを生成

ワークフローをZeebeクラスターへデプロイしたときと同じようにZeebeシンプルモニターを任意のブラウザから起動します。

zeebe_monitor_1.png

さきほどデプロイしたワークフローを選択し、「New Instance」のボタンを押下します。このボタンを押下することによって、内部的にはワークフローインスタンスが生成され、ワークフローの実行が行われます。

zeebe_monitor_new_instance.png

ワークフローの画面を確認すると、ワークフローインスタンスの生成が確認できます。また、先ほど起動したサービスに割り当てられたジョブが完了していることも確認できました。

zeebe_monitor_processed_instance.png

サービスがジョブから新しいタスクを取得するために、ZeebeではgRPCのServer Streaming RPCという方式でクライアントとブローカーは接続され、一度のリクエストで複数のレスポンスを継続的に受け取ることが可能となります。これは、定期的に通信を行い新しいタスクを問い合わせるポーリング方式と比べると、ネットワークを効率的に使うことができ、高速な連携が可能となっています。

Zeebe_server_streaming.png

Zeebeを用いたトランザクションの整合性担保

ここまでのステップでサンプルのBPMNを使ったワークフローの実装をしてきましたが、実際にマイクロサービスのワークフローを構築する場合に考えなければならないことがあります。複数のサービスに跨る一連の取引のなかでどう整合性を保つか、といった問題です。

マイクロサービスアーキテクチャを採用した分散システムの場合、ワークフローの実行にあたり複数のサービスをまたがってトランザクションを実行することになります。こういった分散トランザクションに対処するための方法として、古くから分散トランザクションといった手法が採られてきました。

ですが、分散トランザクションはCAP定理のConsistency(一貫性)が重視された設計となっており、ワークフローの実行に関わるすべてのサービスが正常終了しなければ実行することができないため、Availability(可用性)は多少犠牲になったものとなっています。また、分散トランザクションに参加できるソフトウェアは少なく、各サービスの技術選択の幅が狭くなることにつながることにもなるでしょう。

そのため、マイクロサービスでは高い可用性と技術選択の自由度をサービスへもたらすために、トランザクションの整合性をアプリケーションで制御を行うSagaと呼ばれるパターンを採用することが多くなっています。

Sagaとは

Sagaでは複数のマイクロサービスのローカルトランザクションをシーケンシャルに実行することでワークフローを実現します。また、一連のローカルトランザクションのいずれかでエラーとなった場合には、データの整合性を保つためにエラー以前で実行されたローカルトランザクションを取り消す処理(補償トランザクション)を実行して結果整合性を実現します。

Saga.png

これにより、サービスをまたがる一連のトランザクションの整合性を分散トランザクションを利用せずに実現することが可能となりますが、そのかわりに補償トランザクションの実装と補償トランザクションの進捗状況を監視する必要があります。

Zeebeを用いたSagaの実装

Zeebeではクライアントからクラスターへの業務エラーの通知をもとに補償トランザクションを開始するワークフローを作成できます。業務エラーの通知は、業務エラーを表すThrowError RPCをクライアント側からクラスターへ送信することで実現していくことになります。また、業務エラーを受信したクラスター側では、リクエスト情報から取得したエラーコードをもとに、該当するエラー境界イベントへ処理を遷移させます。

Zeebe_Saga_introduce.png

ワークフロー上での補償トランザクションの表現がこのように可能となるため、定義した補償トランザクションの発生有無や進捗状況を可視化できます。では、業務エラー発生時の補償トランザクションのワークフローの作成、動作確認をステップごとに確認していきましょう。

モデリングの環境準備

先ほどデプロイしたワークフローをベースに、エラー発生時の補償トランザクションのフローを記述します。そのため、まずはBPMNのモデリングを行うアプリケーションであるZeebe Modelerを環境に準備していきましょう。

  • Zeebe Modelerのインストール

ここではWindows環境でのScoopを利用したインストール手順を紹介しますが、その他環境ごとのインストール手順についてはこちらを参照してください。

  scoop install zeebe-modeler

補償トランザクションのワークフローをデプロイ

Zeebe Modelerを利用して補償トランザクションのワークフローを記述します。

  • Zeebe ModelerでBPMNファイルを開きます。

Zeebe_modeler_1.png

  • Fetch Itemsにエラー境界イベントを設定します。ここではエラーコードに"10"と設定しておきます。 Zeebe_modeler_2.png

  • Collect Moneyの補償トランザクションとしてCollect Money Cancelを作成し、終了イベントを作成します。 Zeebe_modeler_3.png

  • 作成したBPMNファイルを保存し、Zeebe Simple Monitorを利用してZeebeクラスターへデプロイします。 Zeebe_modeler_4.png

補償トランザクションへの遷移を確認

以前作成したCollect Moneyに加え、Fetch Itemsのジョブを実行するサービスを作成します。ここでは処理が失敗した状態を疑似的に起こすために、Fetch Itemsのサービスで業務エラーとなるように作成します。

import io.zeebe.client.ZeebeClient;
import io.zeebe.client.api.worker.JobWorker;

public class InventoryService {

    public static void main(final String[] args) throws InterruptedException {
        final ZeebeClient client = ZeebeClient.newClientBuilder().gatewayAddress("localhost:26500")
                .usePlaintext().build();
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            client.close();
            System.out.println("shutdown..");
        }));

        final JobWorker jobWorker = client.newWorker().jobType("inventory-service").handler((jobClient, job) -> {
            System.out.println("accepted a job.");
            jobClient.newThrowErrorCommand(job.getKey()).errorCode("10").send().join();
        }).open();
        System.out.println("Service start.");
        while(true){
            Thread.sleep(1000);
        }
    }
}

Zeebe Simple Monitorでワークフローインスタンスの状態を確認すると、Fetch Itemsでのエラーに反応しCollect Money Cancelの補償トランザクションへフローが移ったことを確認できました。

Zeebe_saga.png

おわりに

Zeebeを利用することで、複数のサービスを連携させて一連の取引を実行するワークフローを簡単に作成することができました。また、Zeebeのモニターを利用することでワークフローの処理状況を可視化することができ、業務エラー発生時の補償トランザクションの進捗状況も簡単に確認することができました。

執筆時点のZeebeの最新バージョンは0.26.0で、メジャーバージョンのリリースはまだされていません。そのため、現時点では本番で利用することは難しいかもしれませんが、今後さらに開発が進みメジャーバージョンのリリースがされた場合には、マイクロサービスのビジネスプロセスの定義、状況監視のワークフローエンジンとして利用していくことが可能となるかもしれません。開発も活発に進められており、今後の動向が注目されます。