この記事はOpenSaaS Studio Advent Calendar 2019の13日目の記事です。
こんにちは。サイバーエージェント OpenSaaS Studioの@Juju_62qです。
先週に引き続き決済基盤でのテスト&リリース戦略について書いていこうと思います。 先週に書いた記事の続きになってしまいますので読んでない方は読んでから本記事をお読みください。
TL;DR
- ロジックの確かさを確かめるためにロジックを独立させて、ユニットテストを行う
- データの疎通やレスポンスに誤りがないかを確認するためにデプロイ前にE2Eテストを行う
- 保存データが正しいかどうか確かめられるといい
決済基盤らしいCI/CDを目指して
先週の記事で決済基盤のCI/CDは "エンドユーザが決済を行った時に正常な額の決済が行える" ということが最も大切であるということをお話ししました。 マイクロサービスアーキテクチャを採用しているアプリケーション開発において、テスト対象は3つあると思っています。
- ロジックの正しさ
- レスポンスの正しさ
- 実行に伴った結果の正しさ
この3つのうちSimplyでは上の2つのテストを現状行っています。 実際に行っていることも踏まえながら、最後の 実行に伴った結果の正しさ を現在はなぜ行っていないのかをお伝えできればと思います。
ロジックの正しさ
Simply Payではアプリケーションを基本的にドメイン駆動で開発をしています1。 これには大きく2つの理由があります。
本記事では基本的に後者の理由を扱って行きます。 なお、基本的にドメインモデルの独立性が高ければそれで十分に要件を満たすため、クリーンアーキテクチャ、オニオンアーキテクチャ、レイヤードアーキテクチャ等の実現方法については強いこだわりを持っていません。
適切に切り分けたドメインモデルはドメインモデル以外のどのプログラムへの依存も存在しないため、正常系、異常系含めて非常にテストが行いやすいです。 したがって適切に分離させたドメインモデルのUnitTestをきちんと記述することで記述したプログラムの論理的な正しさを確かめています。
また、ドメインモデル以外についても依存関係を整理してテストプログラムで適当にDIしてあげれば簡単にテストがかけるのでドメインモデル以外についてもUnitTestをきちんと行って、動作確認を行っています。 かなりシンプル且つ一般的な方法ですね。
補足: Simply Payの開発言語について
Simply Payの開発言語として主にKotlinを採用しています。 KotlinはJavaをベースにしているのでinterfaceやジェネリクスなどチーム開発に必要な機能は揃っていますし、DDDの資料としてJavaをサンプルとしているものは非常に多いです。 この辺りの言語機能や資産は堅牢でテスタブルなシステム開発を行う上で非常に助けになっています。 一方で関数型チックな機能や言語機能も豊富でコードの行数をそれなりに抑えることができます。 マイクロサービスを動かす上でJVMに思うところはないわけではないですが、手続き型言語としては堅牢な型システムと大規模開発に必要な機能を揃えているためロバストなマイクロサービスアプリケーションを開発する上でかなり良い言語かなと思っています。
レスポンスの正しさ
マイクロサービスとしてのロジックが正しいことが確認できても前後の疎通がうまくできない場合など全体を通してレスポンスが正しくない場合、全体として正常に動作せずユーザに即影響が出てしまいます。 この自体を防ぐためにはデプロイ時にE2Eでユーザのシナリオを投げて適切なデータが返ってくるか確認する必要があります。 しかし、これは普通に考えて簡単ではありません。
施策として弊部署のTechLeadが
という記事を書いたり方法を模索していましたがやはりそこまでうまくはいかないです。
Sportifyのテストでも(戦略としてというのもあるが)1つのマイクロサービスでI/Fを確認しています 。
というわけでSimply PayでどうE2Eテストを行っているかというとデプロイする際にその前の段階での全てのシナリオテストが通ることを確認しています。 アプリケーションは基本的に12 Factor Appsに基づいて開発をされているため、ある程度の可搬性があります。
ということでアプリケーションの可搬性を信じて前段階でシナリオテストが通ることを次のステージへのデプロイ基準としています。
イメージとしてはこんな感じです。
シナリオテストを開発に合わせてメンテナンスするのは正直結構なコストになりますが、それをすることでエンドユーザが快適にアプリケーションを利用できることが確かめられるのであればやる価値は十分かなと考えています。 シナリオテストを通すことで、レスポンスの正しさ、冪等性、部分的に結果の正しさを確かめることができます。 これにより、適切なレスポンスを返せないアプリケーションがプロダクションにデプロイされることは基本的にありません。
デプロイはCIOpsを採用しているのでデプロイ時はかなり柔軟に制御をすることが可能です。将来的にはGitOpsを採用する可能性も十分にありますが現状は制御のしやすさと管理コストを考えるとCIOpsで十分だと考えています。
揮発性のあるnamespace活用しないのはなぜ?
これについては現状難しいと考えています。 先週触れたように決済システムはトランザクションの権化のようなシステムで、システムを通してみると夥しい量のステートを管理しなければなりません。 Simply Payの開発では基本的にステートを可能な限りAWSに押し付けています。 結果としてE2Eテストをしたときの影響範囲がK8sやECSのクラスタに閉じないため、揮発性のあるnamespaceを利用したE2Eテストの影響は揮発性のあるコンピューティングリソースに閉じません。 パブリッククラウドにステートを押し付ける設計は基本的に正しいものだと思いますし、現状この設計を変えていくモチベーションもありません。
実行に伴った結果の正しさ
これについては現状Simply Payでは部分的にしか行えていません。 基本的にE2Eテストで確認はしていますが、それはユーザのユースケースとして確認するためのエンドポイントが用意されている場合のみです。 一方でSimply Payはユーザのユースケースとして確認する必要のない状態の変化も管理しています。 よって 実行に伴った結果の正しさ を確認するためには不要なエンドポイントを生やしたり、DBの中身を確認する処理を記述する必要があります。 これは基本的にセキュリティホールにしかなり得ませんし、不要なプログラムは基本的にバグの温床です。 そのような理由から現状実行を思いとどまっています。 将来的には実行する可能性もありますが、現状いい方法が見つかっていないです。何かいい方法があれば教えていただけたらと思います。
まとめ
今回はSimply Payで確認しているテストを紹介しました。
方針としては
- ロジックの正しさを確認するためにビジネスロジックを独立させてUnitTest
- レスポンスの正しさを確認するために前段でE2Eテスト
を行っています。テストプログラムをメンテナンスするには一定のコストがかかりますが、これらによって安心感を得ることができるため結果的に開発者は高速にアプリの開発ができると考えています。 皆さんはどのような確認をすることでバグや不具合の混入を防いでいますか? もしよかったら教えていただけたら幸いです。