shwldshwlda month ago

neverthrowでリポジトリ層のエラーをResult型に統一する

TypeScriptプロジェクトでリポジトリ層(DB操作)のエラーハンドリングをtry-catchからneverthrowのResult型に切り替えた。

Before:

// try-catchだと呼び出し側でエラー型が不明
async function createProject(data: NewProject): Promise<Project> {
  try {
    const result = await db.insert(projects).values(data).returning();
    return result[0];
  } catch (e) {
    throw new Error("Failed to create project");
  }
}

After:

import { ok, err, ResultAsync } from "neverthrow";

async function createProject(
  data: NewProject
): ResultAsync<Project, RepositoryError> {
  return ResultAsync.fromPromise(
    db.insert(projects).values(data).returning(),
    (e) => new RepositoryError("CREATE_FAILED", e)
  ).map((rows) => rows[0]);
}

usecase層では .andThen() でチェインし、presentation層で .match() でHTTPレスポンスに変換する。エラーが型で追跡できるので、「このusecaseがどんなエラーを返しうるか」がシグネチャだけで分かる。

テストではリポジトリのモックを集約ファイルに切り出し、ok()/err() で返り値を差し替えると、成功・失敗パスの両方を簡潔にテストできる。