UseCaseのエラーハンドリングについて
UseCaseの実装をしていると使う側でエラーの場合の処理を分けるという要件は割とラフに出てきます。
例えばレコードが見つからない場合は404、それ以外なら500のステータスにするなど。
そんな時にUseCaseでエラーを返却するときにメッセージやErrorを返却するだけでは実現するのが難しいです。
そこでどうすればUseCaseのエラーで処理を分岐できるのか考えてみました。
前提
今回の記事はTypeScriptでResult型が使えるneverthrowを使っています。
- TypeScript
- nevertthrowを使用
シンプルなUseCase実装例
例えばユーザーのidを引数にして、Repositoryからユーザーを取得するパターン。
もしユーザーが見つからない場合はneverthrowのerrorでメッセージを返却することで使用する箇所からも参照することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13
import { ok, err } from 'neverthorw' class GetUserUseCase { async execute(userId) { const user = await this.userRepository.findById(userId) // userRepositoryはcontructorから注入にする想定 if (!user) { return err('User not found') } return ok(user) } }
この実装でいける場合もあるけど、困るパターンがエラーの種類によって使用する側で処理を変える必要がある場合です。
UseCaseからは別のメッセージを返却できるのでそのメッセージからどういうエラーか判定することは可能です。
でもメッセージは容易に変更されうる要素だし、メッセージを比較してエラー種別を判定するのもかなり微妙。
エラータイプを定義してメッセージと一緒に返却する
エラーメッセージだけでエラーの種別を判定するのが微妙となると、エラータイプを定義してメッセージと一緒に返却するのことで随分わかりやすくなります。
例えばこんな感じのエラータイプを定義。
1 2 3
export const UserErrorTypes = { UserNotFound: 'UserNotFound', }
UseCaseからは先に載せたerrの部分と違いメッセージとエラータイプのオブジェクトを返却するようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
import { ok, err } from 'neverthorw' class GetUserUseCase { async execute(userId) { const user = await this.userRepository.findById(userId) if (!user) { return err({ msg: 'User not found', type: UserErrorTypes.UserNotFound }) } return ok(user) } }
UseCaseのメッセージを使うかどうかはサーバーサイドなのかフロントエンドなのか、設計方針によっても異なるので単純にメッセージ表示以外に使わないパターンも多くあると思っています。
typeを一緒に返却することで使用する側でエラー種別による分岐が可能になります。
UseCaseのエラータイプを使って処理を分岐する
UseCaseで返却したエラータイプは下記のように使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
import { GetUserUseCase } from 'domain/user/usecases/GetUserUseCase' import { UserErrorTypes } from 'domain/user/errors/UserErrorTypes' const getUserUserCase = new GetUserUseCase() const userResult = await getUserUseCase.execute(1) if (userResult.isErr()) { // userResult.error.typeでエラー種別が取得できる if (userResult.error.type === UserErrorTypes.UserNotFound) { throw new Response(null, { status: 404 }) } throw new Response(null, { status: 500 }) }
UseCaseで指定したUserErrorTypesを使用する側で比較して一致した場合に、特定の処理を行うことができます。