Chaos Engineering の概念は、ほとんどの場合バックエンドのコンポーネントに対して適用されます。
Netflix では Chaos Monkey、Gorilla、Kong などのツールを構築することで、様々なシステム、ゾーン、さらにはリージョン全体がダウンした時に、サービスが存続可能であるように継続的にテストを行っています。
さて、Web アプリケーションを構築する上で、サーバサイドだけを気にかけるだけでよいのでしょうか。
信頼性の高いバックエンドインフラストラクチャとアプリケーションを維持することは重要ですが、ユーザーの要求を満たした後でも、うまくいかないことがたくさんあります。
Web ページの読み込みが遅くなる可能性があり、画像やスクリプトのダウンロードに失敗する可能性があり、地理的な距離が大きいと、大幅な遅延が発生する可能性があります。
これらはユーザーエクスペリエンスに大きな影響を与える可能性があり、バックエンドシステムが完全に機能している場合でも発生する可能性があります。
このブログ投稿では、クライアント側のアプリケーション、特に Web サイトにカオス性を注入することにより、ユーザエクスペリメンスの検証を行います。
※ この投稿において「レスポンス遅延」「サーバサイドエラー」をカオス性として注入します。
msw を利用したモックデータの利用
まずはじめに、バックエンドサーバと接続せずに、サーバからのレスポンスをモックとして取得する方法について検討しましょう。
モックサーバとして近年注目を集めている msw (mock service worker) を紹介します。
msw (mock service worker) はブラウザからのリクエストを Service Worker がインターセプトし、任意のレスポンスを返すことが出来るライブラリです。
これを使用することで、バックエンドと切り離してフロントエンドだけを開発できます。
例えば以下のように、setupWorker
の中にモックの API を定義しておき、 worker.start()
するだけでモックで定義した API に合致するリクエストは msw がインターセプトしてモックのレスポンスデータを返すことができます。より詳細には 公式ドキュメントの Getting Started を参照すると良いでしょう。
// src/mocks.js
// 1. Import mocking utils.
import { setupWorker, rest } from 'msw'
// 2. Define request handlers and response resolvers.
const worker = setupWorker(
rest.get('https://github.com/octocat', (req, res, ctx) => {
return res(
ctx.delay(1500),
ctx.status(202, 'Mocked status'),
ctx.json({
message: 'Mocked response JSON body',
}),
)
}),
)
// 3. Start the Service Worker.
worker.start()
この例では、https://github.com/octocat
へのリクエストを msw がインターセプトし、モックデータをレスポンスとして返却しています。
msw にカオス性を注入する
msw を使用してフロントエンドにおけるモックサーバを構築できました。このモックサーバにカオス性を注入します。「レスポンス遅延」「サーバサイドエラー」といった異常をランダムに返すことで、ユーザエクスペリメンスを検証します。
msw-chaos-composition を使用することで、API リクエストに対しランダムに遅延やエラーを発生させることができます。
インストール
npm または yarn でインストールします。
npm install msw-chaos-composition
yarn add msw-chaos-composition
使用例
例えば、以下のような msw 実装を考えましょう。
これは /hello
に対してリクエストすると、レスポンスコード 200 で { message: "hello world"}
が返却されるシンプルなモック実装です。
const handler = rest.get("/hello", (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({
message: "hello world",
})
);
});
const server = setupServer(handler);
server.listen();
msw-chaos-composition の使い方は非常にシンプルです。 以下のように res()
を chaosRes()
に置き換えるだけです。
import { createChaosResponse } from 'msw-chaos-composition'
const chaosRes = createChaosResponse();
const handler = rest.get("/hello", (req, res, ctx) => {
return chaosRes(
ctx.status(200),
ctx.json({
message: "hello world",
})
);
});
const server = setupServer(handler);
server.listen();
これにより、/hello
にリクエストするとランダムに以下のレスポンスを返すようになります。
{ status: 429, body: "Too Many Requests", }
{ status: 500, body: "Internal Server Error" }
{ status: 502, body: "Bad Gateway" }
{ status: 503, body: "Service Unavailable" }
{ status: 504, body: "Gateway Timeout" }
レスポンスエラーを指定する
実際にプロダクトで利用する場合、エラーの型が決まっているかもしれません。その場合でも、レスポンスを自由に設定することができます。
また、delay
や rate
を指定することで、遅延時間(ミリ秒)やエラーの発生率を制御できます。
const errors = [
{
status: 500,
body: JSON.stringify({ message: "Internal Server Error", code: "INTERNERL_SERVER_ERROR" }),
delay: 500,
rate: 10,
},
{
status: 504,
body: JSON.stringify({ message: "Gateway Timeout", code: "GATEWAY_TIMEOUT" }),
delay: 100000,
rate: 20,
},
];
const chaosRes = createChaosResponse(errors);
const handler = rest.get("/hello", (req, res, ctx) => {
return chaosRes(
ctx.status(200),
ctx.json({
message: "hello world",
})
);
});
const server = setupServer(handler);
server.listen();
このように定義することで、ランダムに以下のレスポンスを返すようになります。
{
status: 500,
body: {
message: "Internal Server Error",
code: "INTERNERL_SERVER_ERROR"
}
}
{
status: 504,
body: {
message: "Gateway Timeout",
code: "GATEWAY_TIMEOUT"
}
}
React アプリケーションに組み込む
さて、ここからはランダムにエラーや遅延を返すことにより、アプリケーションの振る舞いがどのようになるか、そしてユーザーエクスペリメンスを向上させるには何をするべきか見ていきましょう。
例とするサンプルアプリケーションを紹介します。このアプリケーションはブログの一覧を表示するシンプルな機能を持っています。
このブログ一覧を取得する API 通信にカオス性を注入することでアプリケーションがどのように振る舞うか観測してみましょう。
サーバエラーが発生した場合、画面に何も表示されなくなってしまいました。これではユーザはどうしたら良いかわかりません。操作を続行することすらできなくなってしまいます。
また、レスポンスに大幅な遅延が発生した場合はどうでしょうか?通信中ユーザは何が起きているのかを理解できません。ひょっとするとブラウザのリロードを試みるかもしれませんし、Webサイトに飽きてしまい、退会してしまう可能性もあります。
ユーザエクスペリメンスを向上するために、以下の対応を組み込んでみましょう。
- API通信でサーバエラーが返ってきた場合はユーザにエラーメッセージを表示する
- ローディング中であることをユーザがわかるように表示する
対応した結果はつぎのようになります。
ずいぶんとマシなアプリケーションになりました。プロダクションレベルではチープすぎる実装ですが、少なくとも、msw-chaos-composition の振る舞いを紹介するには十分でしょう。
さいごに
フロントエンドにおけるカオスエンジニアリングとして、「レスポンス遅延」「サーバサイドエラー」をカオス性として注入して開発する方法を紹介しました。
今回紹介したサンプルアプリケーションは非常にシンプルで、1つのAPI通信しか考慮していませんでした。実際のプロダクトではより複雑なアプリケーションとなり、いくつものコンポーネント、様々な種類のAPI通信が発生するでしょう。1つのAPI通信が遅延した場合、他のコンポーネントのレンダリングにどう影響するか、ユーザエクスペリメンスはどう低下してしまうのか、挙げればキリがないかもしれません。
また、フロントエンド開発は闇が深く、これだけに限りません。ネットワークが途中で切れた場合、サーバまでリクエストが到達しなかった場合、スマートフォンからの接続、3G回線など、、、
いずれにせよ、私たちは HTTP で繋がった Web の世界と向き合っていかなければなりません。
サーバサイドをまるまるカオスエンジニアリングした状況で、フロントエンドもテストする という開発手法は、大掛かりすぎて導入が難しいかもしれませんが、今回紹介したように フロントエンドだけにカオスエンジニアリングのプラクティスを導入する という方法なら手軽に導入ができ、ある程度効果も見込めるかもしれませんね。
参考
サンプルアプリケーションのソースコード