見出し画像

Pactで小さく始めるコンシューマ駆動契約テスト

Ubie(ユビー)株式会社でソフトウェアエンジニアをしている八木(@sys1yagi)です。現在Ubieはソフトウェアエンジニアが25名おり、採用活動の進捗としてはまもなく30名を超えることが見えてきています。

Ubieには大きく2種類のプロダクトがあります。病院向け(toB)の「AI問診ユビー」というプロダクトと、一般ユーザ向け(toC)の「AI受診相談ユビー」というプロダクトです。病院向けの「AI問診ユビー」は、病院、クリニック、グローバル(アジア)に分かれており、全体で4つのプロダクトを開発しています。これらのプロダクトは15個のサービスを組み合わせて動作しています。

画像1

プロダクトとサービスの関係図

プロダクト間連携で起こる課題

現在はサービス群がざっくりプロダクトのバーティカルで分かれていて、一部で共通のサービスがあったり、プロダクト間で連携しあう部分が出てきたといった状態です。人数やチームがこれからどんどん増えていくことに備えて本格的にマイクロサービスアーキテクチャとして整頓をし始めています。

プロダクト間で連携しあう部分において、変更によるクリティカルな問題の発生が多くなっており、まだまだ不確実性が高く素早く検証をしたいにも関わらず実行のコストが上がってしまっています。連携部分の堅牢性や変更容易性を向上するには次の要件を満たす必要があります。

・1. サービスの利用者(クライアント)の要求や変化をサービスの供給者(API)が適切に認識し続ける
・2. サービスの供給者の変更によって、サービスの利用者の期待からズレてしまうことを防ぐ

そこでコンシューマ駆動契約テストの導入を検討し始めました。

コンシューマ駆動契約テストは何をして、何をしないか

コンシューマ駆動契約テストはサービスの利用者(コンシューマ)とサービスの供給者(プロバイダ)の間の送受信の内容を契約(コントラクト)として記載し、それらが共通の理解に準拠しているかをテストすることで、コンシューマとプロバイダ間の通信I/Fが壊れていないことを保証します。コンシューマ駆動契約テストはユニットテストで記述するので、作成や実行のコストがE2Eテストに比べて安いほか、問題発見のタイミングも早くなります。

コンシューマ駆動契約テストは何をするか

■コンシューマ主導でどのようにプロバイダを利用するかを契約する
※ここで重要なのはコンシューマがどのような内容で通信をしているかであって、コンシューマがどのような内容で通信をしたいかではないという点です。契約には通信内容の事実を記載します。仕様の追加変更などについてはこのプロセスではサポートしません。
■コンシューマの作った契約をプロバイダが満たしているか検証する
■コンシューマが契約を更新したら、プロバイダがそれを満たしているかを検証する
■プロバイダはコンシューマの契約に基づいて自身の変更が契約に違反していないことを保証する
■契約の違反を検出する

コンシューマ駆動契約テストは何をしないか

■パフォーマンスと負荷のテスト
■Public APIの保証 (コンシューマが不特定多数の場合は機能しない)
■プロバイダの仕様の決定(ただし示唆は与える)
■契約の違反に対応する(検出までで、デプロイをできなくするとか修正するといったことは人間が行う)

コンシューマ駆動契約テストツール「Pact」

コンシューマ駆動契約テストツールはいくつかあるようですが、調べた限りではPactがデファクトスタンダードのようです。

最初にRubyで実装され、その後様々な言語をサポートしています。UbieではRuby on RailsとSpring Boot/Kotlinを使っているので、Pact RubyPact JVMを使うことになります。

Pact導入の2段階

コンシューマの書いた契約をプロバイダーが受け取って検証を行うわけですが、契約を常に同期しなければ契約に違反したかを検出できません。

スクリーンショット 2021-01-27 10.27.40

コンシューマとプロバイダで契約を同期する必要がある

PactはPact Brokerという契約ファイルを中継するアプリケーションを提供しており、コンシューマとプロバイダ間の契約の同期を可能にします。

スクリーンショット 2021-01-27 10.49.33

Pact Brokerは契約の同期の問題を解決しますが、Pact BrokerやCIの用意はやや負担が大きく感じます。コンシューマとプロバイダ間の契約が役に立つかをまずは検討したいので、次の二段階で導入を進めるのがよいのではないかと考えました。

1. コンシューマとプロバイダの契約検証を手動で運用する
2. コンシューマとプロバイダの契約検証をブローカーを使って自動化する

幸いPactの契約ファイルは一つのJSONファイルなので、手動での運用が可能です。

小さく始める

「コンシューマとプロバイダの契約検証を手動で運用する」方法は非常に単純です。

・コンシューマでPactテストを書く
・Pactテストで生成した契約ファイルをプロバイダにコピーする
・プロバイダのPactテストで契約ファイルを読み込んで検証する

コンシューマでPactテストを書く

コンシューマ側はドキュメントに従ってPact用のユニットテストを書くだけなので手軽です。次のリンクはPact JVMのJUnit5向けのドキュメントです。

コンシューマ用のテストでリクエストとレスポンスを定義すると、モックサーバを自動的に起動して実際の通信を試せます。もしかしたらコンシューマ用テストを書くだけでも示唆を得られるかも知れません。

Pactテストで生成した契約ファイルをプロバイダにコピーする

テストを実行すると契約を記述したJSONファイルが生成されます。生成されたファイルをプロバイダにコピーすればプロバイダ側のテストができます。Pact JVMの場合は契約ファイルは次の場所に保存されます。

// テストを実行して契約ファイルを生成する
./gradlew check

// 契約ファイル群が保存されるディレクトリ
build/pacts

プロバイダ側で契約ファイルをコピーするスクリプトを用意すればよいです。

プロバイダのPactテストで契約ファイルを読み込んで検証する

プロダクト側のテストも、ローカルの契約ファイルを使う場合は非常に単純です。次のリンクはPact Rubyでのプロバイダの検証についてのドキュメントです。RubyではRakeタスクを使います。

spec/service_consumers/pact_helper.rbで契約ファイルを指定すると、契約に沿ってテストを実行してくれます。

require 'pact/provider/rspec'

# 契約のテストのセットアップをするファイル
require 'service_consumers/provider_states_for_my_service_consumer'

Pact.service_provider "My Service Provider" do
 honours_pact_with 'My Service Consumer' do
   # Pactの契約ファイルのパス
   pact_uri 'specs/pacts/my_consumer-my_provider.json'
 end
end

契約ファイルにはリクエスト内容が記載されているので、Rakeタスクの中でプロバイダに対して実際のリクエストを行い、レスポンスを検証します。契約内容によっては特定の条件(データの有無など)に依存するケースがあるので、セットアップ用のファイルを用意します。特定の状態はコンシューマ側のテストで記述します。

# service_consumers/provider_states_for_my_service_consumer.rb

Pact.provider_states_for 'My Service Consumer' do
 provider_state 'data count > 0' do
   set_up do
     # データを用意する
   end
 end
end

rakeタスクですべての契約が正しく動作するか検証できれば完了です。契約が同期的でないのでコピーし忘れたら検証が漏れてしまうという問題は残りますが、これだけでも十分に役に立ちます。実際にこの手順で導入をしていく中で、返却値のキーに誤りがある部分を見つけることができました。

プロダクトやチームの境界間の通信の部分について、コンシューマ駆動契約テストを導入しまずは手動で運用する、というアプローチは手軽で十分な効果を発揮できるのではないかと思います。

いつPact Brokerを導入するか

不確実性が高く素早く検証をしたい部分を安全に変更するという目的を果たすには手動運用で十分そうです。ではどんなタイミングでPact Brokerによる自動化を行うとよいでしょうか。なぜ手動運用で十分そうなのかを考えてみると、手と目の数が十分にあるからと言えそうです。変更が活発で壊したくないという要求が強い場合は自ずと契約ファイルの適切な同期を行うことを期待できます。もちろん更新忘れの事故は起き得ますが十分にやりくりできるのではないかと思います。

翻って不確実性が下がり変更の頻度が下がってくるとどうなるか。手と目の数が減り、時間経過の試練が訪れます。契約の変更忘れや同期忘れのほかコンテキストなども失われてしまったりします。こうした状態になる前に導入をするのがよさそうです。この辺りのROIの判断は環境によって変わってくるだろうと思います。

ところでPact Brokerも小さく始められます。PactflowというPact Brokerを提供するサービスがあります。自前でホスティングすることを考える前に使ってみると良いかなと思います。手動運用が軌道に乗ったら運用のコスト感を見るために一度試してみようかと考えています。

まとめ

本エントリではプロダクトやチーム間で発生する通信に関する課題を、コンシューマ駆動契約テストを小さく始めることで解決していくということについて説明しました。Ubieでは引き続き不確実性の検証にフォーカスしながら、技術的な課題についても取り組んでいきます。

もしUbieの事業やプロダクト、開発チームなどに興味が湧いたらTwitterでリプライやDMしたり、採用サイト経由でカジュアル面談をしましょう!ぜひお気軽にお声がけください。



この記事が気に入ったらサポートをしてみませんか?