🐥

TypeScript×クリーンアーキテクチャで簡単なタスク管理アプリを作成した

2024/12/31に公開

n 番煎じかもしれませんがクリーンアーキテクチャについての学習のアウトプットも兼ねて TypeScript で簡単なタスク管理アプリケーションを作成しました。

https://github.com/guitarinchen/clean-architecture-todo-app

使用技術

ディレクトリ構成

上層レイヤから infrastructure, presentation, application, domain の順

./src
├── application  // アプリケーション層。ユースケースを定義し、DTO を使用してデータの転送を管理。
│   ├── dtos  // データ転送オブジェクトを格納するディレクトリ
│   │   └── TaskDTO.ts
│   └── useCases  // ユースケースを定義するディレクトリ
│       ├── CreateTask.ts
│       ├── DeleteTask.ts
│       ├── FindAllTasks.ts
│       ├── FindTaskById.ts
│       └── UpdateTask.ts
├── domain  // ドメイン層。ビジネスロジックやルールを定義。
│   ├── entities  // ドメインエンティティを格納するディレクトリ
│   │   └── Task.ts
│   ├── errors  // エラー管理を行うためのクラスを格納するディレクトリ
│   │   ├── DatabaseError.ts
│   │   ├── UnknownError.ts
│   │   └── ValidationError.ts
│   ├── repositories  // リポジトリインターフェースを定義するディレクトリ
│   │   └── TaskRepository.ts
│   └── services  // ドメインサービスを定義するディレクトリ
│       └── TaskService.ts
├── index.ts  // アプリケーションのエントリーポイント
├── infrastructure  // インフラ層。外部システムとのやり取りを管理。
│   └── repositories  // リポジトリの実装を格納するディレクトリ
│       └── Prisma  // Prisma を使用したリポジトリの実装
│           └── PrismaTaskRepository.ts
└── presentation  // プレゼンテーション層。ユーザーとのインターフェースを管理。
    ├── controllers  // コントローラーを格納するディレクトリ
    │   ├── BaseController.ts
    │   └── TaskController.ts
    └── routes  // ルーティングを定義するディレクトリ
        └── taskRoutes.ts


リポジトリ実装のエラーハンドリング

リポジトリ実装で例えば Prismanode-postgres 等のパッケージに依存したエラーをそのまま投げてしまうと、内側の層が一番外側の infrastructure 層に依存するという依存性の逆転が発生してしまいます。そのためリポジトリ実装で発生したエラーは domain 層で定義したカスタムエラーに変換することで依存性の逆転が発生しないようにしました。

export class PrismaTaskRepository implements TaskRepository {
  constructor(private readonly client: PrismaClient) {}

  private handlePrismaError(e: unknown): Error {
    // エラー内容をログに残す
    console.error(e);
    // Prisma ORM に依存したエラーをカスタムエラーに変換
    if (e instanceof PrismaClientValidationError)
      return new ValidationError(e.message);
    if (e instanceof PrismaClientKnownRequestError)
      return new DatabaseError(e.message);
    if (
      e instanceof PrismaClientUnknownRequestError ||
      e instanceof PrismaClientRustPanicError
    )
      return new UnknownError(
        "An unknown error occurred while trying to fetch tasks",
      );

    return new UnknownError(
      "An unknown error occurred while trying to fetch tasks",
    );
  }

  async findAll() {
    try {
      const tasks = await this.client.task.findMany();
      return tasks.map((task) => new Task(task));
    } catch (e) {
      // エラーハンドリング
      throw this.handlePrismaError(e);
    }
  }

  // 以下省略
}

おまけ

Hono

本筋とは逸れますが今回採用した Web フレームワーク Hono は Web 標準に準拠しています。そのため Web 標準の Request, Response, etc... を利用したコントローラを容易に作成することができました。例えば有名な Web フレームワークである Express では独自の Request や Response を採用しているため、それらを利用したコントローラを作成してしまうとフレームワークを変更した際にコントローラの実装も大きく変更する必要が出てくるかもしれません。

参考

Discussion