KJ DEV
コード設計

UseCaseのエラーハンドリングについて

UseCaseの実装をしていると使う側でエラーの場合の処理を分けるという要件は割とラフに出てきます。

例えばレコードが見つからない場合は404、それ以外なら500のステータスにするなど。
そんな時にUseCaseでエラーを返却するときにメッセージやErrorを返却するだけでは実現するのが難しいです。

そこでどうすればUseCaseのエラーで処理を分岐できるのか考えてみました。

目次
  1. 前提
  2. シンプルなUseCase実装例
  3. エラータイプを定義してメッセージと一緒に返却する
  4. UseCaseのエラータイプを使って処理を分岐する

前提

今回の記事はTypeScriptでResult型が使えるneverthrowを使っています。

  • TypeScript
  • nevertthrowを使用

シンプルなUseCase実装例

例えばユーザーのidを引数にして、Repositoryからユーザーを取得するパターン。
もしユーザーが見つからない場合はneverthrowのerrorでメッセージを返却することで使用する箇所からも参照することができます。

domain/user/usecases/GetUserUseCase.ts
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からは別のメッセージを返却できるのでそのメッセージからどういうエラーか判定することは可能です。
でもメッセージは容易に変更されうる要素だし、メッセージを比較してエラー種別を判定するのもかなり微妙。

エラータイプを定義してメッセージと一緒に返却する

エラーメッセージだけでエラーの種別を判定するのが微妙となると、エラータイプを定義してメッセージと一緒に返却するのことで随分わかりやすくなります。

例えばこんな感じのエラータイプを定義。

domain/user/errors/UserErrorTypes.ts
1
2
3
export const UserErrorTypes = {
  UserNotFound: 'UserNotFound',
}

UseCaseからは先に載せたerrの部分と違いメッセージとエラータイプのオブジェクトを返却するようにします。

domain/user/usecases/GetUserUseCase.ts
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を使用する側で比較して一致した場合に、特定の処理を行うことができます。

開発で参考になった本

実際に読んでみて開発に役立った本を紹介しています。

これから本格的にデザインシステムを学んで作りたい時にとても参考になる一冊でした。デザインシステムついて幅広く触れられているけど、「tailwindを触ってデザインシステムに興味を持った」という人でも少しずつ取り入れやすいです。