こんにちは、エンジニアのタカです。
普段はアジャイル開発におけるスクラムマスターや開発者としてプロダクトの開発に関わっています。
今回は、プロダクト開発で起きたシステム課題に対して、導入の敷居が低いスプレッドシートを中心に解決を行った体験談を書きたいと思います。
エラーメッセージに関する課題
現在関わっているシステムにおいて、ある日、エラー制御に関する問題点が開発チームから上がりました。
1. 画面で表示されるメッセージの統一性が欠けている
2. システム共通のエラーコードが存在しない
詳細を解説します。
1.メッセージの統一性
例えば、データを一覧表示する画面でデータ登録件数が0件だった場合、通常は「データが登録されていません」という趣旨のメッセージが画面に表示されます。
データの種別が異なる場合でも、同じ事象のメッセージには統一性を持たせたいところですが、現在関わっているシステムでは、フロントエンドで一部メッセージをハードコーディングしており微妙に表記揺れが存在していました。
これだけだと共通処理を作って解決すればよいとはじめは思いましたが、このメッセージは運用を続ける中で、内部/外部からの指摘で変わる可能性があり、また複数言語向けにメッセージも管理したいなど、単に共通処理を作って解決は難しい状況になっていました。
2.システムのエラーコード
Webシステムで使うエラーコードの代表例はHTTPステータスコードです。
例えば400エラーはBadRequest
という意味ですが、複数の画面でそれぞれ起こり得る可能性があり、どの画面で400エラーが発生したかをログ等から特定したいとのことでした。
もちろん、ログの設定なり組み合わせで特定は可能ですが、パッと見て分かりにくいですし、運用メンバーがメッセージからどの画面の事象かを特定できれば運用コストの削減になります。
問題の解決方法と要件
これらの問題の解決方法として、システム全体で使うエラーコードとフロントエンドで表示するメッセージを紐づけて管理することにしました。
運用を加味して要件をまとめたところ、下記の通りとなりました。
1. 定義するデータの保存場所は、DB、テキストなど形式は問わない。
ただし、システムとして、フロントエンドとバックエンドでそれぞれ参照したい。
2. エラーコードに紐づくメッセージは、POをはじめ関係者が閲覧でき、場合によっては編集したい。
3. 今後、新機能が実装されるたびにデータを追加したい。
3.に関しては、運用次第でメッセージを変える可能性があること、運用や評価を行う上で開発者以外のメンバーも閲覧できることが必要なためとなります。
システム構成
シンプルな構成になりますが、後でマニュアルとして残すために、GoogleCloud Developer cheat sheetを用いてシステム構成図を作成しました。
データの保存場所は、手軽に使えるスプレッドシート採用しました。
スプレッドシートは使用する敷居がとても低く、アクセス権を設定することで、直接開発に関わらないメンバーも閲覧/編集することが可能ですし、Jsonなどのテキストファイルと比べるとリポジトリへのアクセス権も不要で視認性も良いです。
ただし、スプレッドシートをシステムから直接参照するのはAPIを介した通信により様々なコストがかかるため、GAS (Google Apps Script)でJsonを生成してリポジトリにcommitする形式を採用しました。
GASのサンプルコードと注意点
GASの処理はシンプルで、フロントエンド用とバックエンド用に関数を2つ作り、後の処理は共通化して別関数に切り出しました。
一部、エラー制御は省いた主要処理のサンプルコードを掲載します。
// Slackにメッセージを送信
function notifySlack(message, filePath, type) {
// SlackのWebhook URL、チャンネル名を指定
const slackWebhookPath = PropertiesService.getScriptProperties().getProperty('SLACK_WEBHOOK_PATH');
const requestUrl = "<https://hooks.slack.com/services>" + slackWebhookPath;
// Google Driveの特定のフォルダにファイルを作成
const folderId = PropertiesService.getScriptProperties().getProperty('DRIVE_FILE_PATH');
const folder = DriveApp.getFolderById(folderId);
const file = folder.createFile('error_code.json', JSON.stringify(message), MimeType.PLAIN_TEXT);
// 共有リンクを作成してSlackに送信
const url = file.getUrl();
const payload = {
text: type + 'JSONを生成しました。共有リンクです:' + url + '\\n' + filePath + 'に貼り付けてください'
};
const options = {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload)
};
UrlFetchApp.fetch(requestUrl, options);
}
// JSON生成処理
function generateJson(getErrorCodeAndInfo, filePath) {
// スプレッドシートのデータを取得する
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('data');
const data = sheet.getDataRange().getValues();
// JSONを格納するオブジェクトを作成する
const json = {};
// シートの各行を処理する
for (let i = 2; i < data.length; i++) {
const row = data[i];
const { errorCode, errorInfo } = getErrorCodeAndInfo(row);
// エラーコードをオブジェクトに追加する
json['data'][errorCode] = errorInfo;
}
return json
}
// バックエンド用の共通エラーコード取得処理
function backendStatus(row) {
// エラーコードを取得する
const errorCode = row[2];
// エラー情報を取得する
const errorInfo = {
'number': row[3],
'httpStatusCode': row[4],
'httpStatus': row[5],
'message': row[6]
};
return { errorCode, errorInfo };
}
// フロントエンド用の処理
function frontendMessage(row) {
// エラーコードを取得する
const errorCode = row[2];
// フロントエンドメッセージの取得
let message = '';
if (row[7]) {
message = JSON.parse(row[7]).ja
}
// エラー情報を取得する
const errorInfo = {
'message': message
};
return { errorCode, errorInfo };
}
// バックエンド用の関数
function generateErrorCodeJson() {
const filePath = ''; // JSONのパスを書く
const type = 'システムエラーコード'
const message = generateJson(backendStatus, filePath);
notifySlack(message, filePath, type);
}
// フロントエンド用の関数
function generateFrontMessageJson() {
const filePath = ''; // JSONのパスを書く
const type = 'フロントエンドエラーメッセージ'
const message = generateJson(frontendMessage, filePath);
notifySlack(message, filePath, type);
}
初期段階ではGASで生成したJSONをSlackにテキスト送信していましたが、ある程度の長さに達したら省略されてしまったため、途中でDriveに切り替えました。
また、JSONは手動でソースに反映しcommitが必要ですが、将来的にこの運用が長く組み込まれるようになった際は、CI/CDで自動で生成し配置なども検討します。
メリットとデメリット
この構成のメリットは、利用の敷居が低くシンプルな構成であること、運用における変更や拡張の要望にとても柔軟に対応できることが挙げられるます。
GASなら、例えばチーム内でエンジニアメンバー以外が管理を引き継ぐ際にも、ブラウザがあればデバッグやデプロイまで出来るため、ChatGPT等でのサポートによりメンテナンスがし易いと考えています。
clasp + TypeScriptで書くのが理想ではありますが、先述の理由に加え、運用途中でシートの形式が変わることが想定され、加えてLambdaやCloudFunctionsなどに移行する未来も有り得ることから、一旦はGASのまま管理することにしました。
デメリットとして、手軽すぎる構成なので壊れやすいという点が上げられます。
例えばスプレッドシートを想定外の形式で編集された場合、GASの作りによりますが、実行が失敗してしまいます。
この場合、バリデーションを行う処理を別で準備したり、入力規則や編集不可な箇所を設定することで対策を行えます。
おわりに
今回の内容は以上となります。
既存の困っていることを解決したいという声が開発チーム内で上がったことがきっかけでしたが、運用を含めて最初はカッチリとした仕組みにせず、スピード感を持って導入し、本格運用したら徐々に見直す形をとりたいと考えています。
今回の内容がプロダクト運用において何かの参考になれば幸いです。
<おすすめ記事・メディア>