n 番煎じかもしれませんがクリーンアーキテクチャについての学習のアウトプットも兼ねて TypeScript で簡単なタスク管理アプリケーションを作成しました。
使用技術
- TypeScript (言語)
- Hono (Web フレームワーク)
- Prisma (ORM)
- Biome (linter, formatter)
ディレクトリ構成
上層レイヤから 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
リポジトリ実装のエラーハンドリング
リポジトリ実装で例えば Prisma や node-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 を採用しているため、それらを利用したコントローラを作成してしまうとフレームワークを変更した際にコントローラの実装も大きく変更する必要が出てくるかもしれません。