概要
TIG DX所属の多賀です。最近は設計をしつつ Go も触れて引き続き楽しく仕事してます。
今回は、errors package を一部利用して、エラーコードベースのエラーハンドリング処理を実装しました。また、morikuni/failure を利用した実装への書き換えも試してみています。
エラーコードベースの例外ハンドリングについて
前提としてGoで書かれた HTTP APIサーバーに対してのエラーハンドリングについて記載します。
エラーコードベースの例外ハンドリングについてですが、アプリケーションで発生するエラーを事前にラベリングしてコード化し、コードをもとにエラーハンドリングを実施することとします。発生時の運用対応や影響について、事前に一覧で整理することで、運用負荷を下げる意味があると考えています。(補足: Futureではメッセージコードと呼称することが多いですが、一般的な命名であるエラーコードで統一します)
以下のような形で整理しています。
実際は、エラーコード別に運用アクションも合わせて整理します。
エラーコード表 (例)
エラーコード | エラー名 |
---|---|
XXX0001 | クライアントエラー |
XXX0002 | DBコネクションエラー |
XXX0003 | 外部APIサーバーへのリクエストエラー |
エラーコードを利用した際に重要なことは、エラーコード外のエラーを発生させないことにあると考えています。エラーコード外のエラーが発生した際、何をどうしたらよいかが明文化されていないためです。エラーは、ログより発生を検知し対応するものとした際に、いかにアプリケーションから出力されるログに対して、適切にエラーコードを付与できるかが大事です。
errors package を利用した実装例
アプリケーション側での、コンパイルレベルでの制約は難しくコードレビューでの担保もふくまれますが、以下のようにしてエラーを出力しています。
パッケージの構造としてはシンプルな以下のイメージです。
. |
エラーコード別のエラーを定義
package apperror |
handler 層に返却される error を必ずエラーコード対応Error型とする
各層のerror を wrappingして handler 層に返却します。ここは愚直にやらないといけないところです。(静的解析ツールを作ってチェックする機構を用意するほうがより良いですね。)
関数の戻り値の第2引数自体を AppError
型にすることも考えられますが、標準 error インタフェースを尊重したほうが良いとのノウハウがあるので対応しませんでした。
参考: 初めてGolangで大規模Microservicesを作り得た教訓
package service |
エラーログを出力する箇所を集約
handler 層に集約させます。
package handler |
上記の通りに実装することで、エラーコードにエラーを集約すること自体はできました。
ただ、独自エラーを定義して Wrapするところはもっと書きやすくできないか、検討の余地がありそうだと感じました。
morikuni/failure を利用できないか?
morikuni/failure は morikuni さんが作成されたエラーハンドリング向けのライブラリです。errors package 存在前より開発されているライブラリです。
https://github.com/morikuni/failure
Package failure provides an error represented as error code and extensible error interface with wrappers.
とのことなので、エラコードベースの利用にマッチしそうです。
以前の Go Conference 2019 Spring にて発表されている資料 にて、failure と errors (当時は xerrors) の使い分けについて明確に説明されています。とてもわかりやすくて、しっくりきたことを覚えています。
参考: https://speakerdeck.com/morikuni/designing-errors?slide=33
やってみた
morikuni/failure を利用して上記のコードを書き換えてみました。
エラーコード別のエラーを定義
とてもシンプルですね。追加も簡単になりそうです。
package apperror |
handler 層に返却される error を必ずエラーコードに対応させた独自エラーとする
morikuni/failure でも、エラーコードへの変換 ( failure.Translate
) や エラーコードの Wrap ( failure.Wrap
) は可能です。
(ちなみに、failureで生成したエラーも errors package のインタフェースを満たしています。)
package service |
エラーログを出力する箇所を集約
README の sample を参考にハンドリング処理を実装してみました。
package handler |
感想
エラーコードベースの例外ハンドリングのTipsについて記載しました。
failure を利用したほうがよりシンプルに書けて良いのではないかと感じています。
また、他のメリットとしては以下がありそうです。
- failureでWrapすることでスタックトレースが残る
- failureの便利関数を利用して Error のコンテキストを文字列以外の形式で作成できる
failureは実戦で使えてないので、次回チャレンジしてみたいです。