クリーンアーキテクチャでWebサーバーもDBもないTodoアプリを作成する
目次
概要
今回はクリーンアーキテクチャでWebサーバーもDBもないアプリケーションを作成します。
作成するのはTodoアプリです。
Todoアプリ程度だとクリーンアーキテクチャのメリットはあまり感じれないかも知れません。
なぜWebサーバーもDBもなしにアプリケーションを作成するのかについてですが、クリーンな設計を目指す際にWebサーバーやDB、フレームワークに依存しない独立した実装が必要であるというのが理由になります。
より詳しい説明やクリーンアーキテクチャの詳細に関しては、ロバート・C・マーチン著『Clean Architecture 達人に学ぶソフトウェアの構造と設計』を参考にしてください。
良くある同心円状のあの図も載せません。
実装にはtypescriptを使用しており、その型システムをより活かすために、出来るだけ型レベルでの実装を目指しました。
ドメイン
ドメインとしてはTodoをエンティティとして定義します。
また、エンティティとしての抽象も定義しておきます。
type Entity = {
readonly id: number;
};
type Todo = Entity & {
readonly content: string;
readonly isCompleted: boolean;
};
ユースケース
type GetTodoTransaction = (
id: Todo["id"],
repository: TodoRepository
) => Promise<Todo>;
export const GetTodoTransaction: GetTodoTransaction = async (id, repository) => {
return await repository.selectTodo(id);
};
ここではではTodoを取得のみを載せています。
実際のユースケースが抽象となる型エイリアスを持ち、インターフェースアダプタ用の型エイリアスを引数として注入しています。
インターフェースアダプタ
type TodoRepository = {
selectTodo: (id: Todo["id"]) => Promise<Todo>;
};
import { Client } from "https://deno.land/x/mysql/mod.ts";
export class ClientContext implements TodoRepository {
private client;
public constructor() {
this.client = new Client().connect({
hostname: "127.0.0.1",
username: "root",
db: "dbname",
password: "password",
});
}
public async selectTodo(id: Todo["id"]): Promise<Todo> {
const { rows: todo } = await this.client.query(`select * from todo where id = ${id}`);
return todo;
}
}
export class MockClientContext implements TodoRepository {
private Todo: Todo[] = [];
public selectTodo = async (id: Todo["id"]): Promise<Todo> => {
const data = this.Todo.find((i) => i.id === id);
if (data === undefined) throw new Error("Todo is not exist");
return await data;
};
}
クリーンアーキテクチャの利点としてDBに依存しない実装となるため、モックオブジェクトをDBとして使用するためのクラスを定義します。
テストコード
import { assertEquals } from "https://deno.land/std@0.204.0/assert/mod.ts";
import {
CreateTodoTransaction,
DeleteTodoTransaction,
} from "../UseCase/TodoTransaction.ts";
import { MockClientContext } from "../DB/MockClientContext.ts";
Deno.test("Todo Test", async (t: Deno.TestContext) => {
await t.step("Get Todo Test", async () => {
const db = new MockClientContext();
const data = { content: "掃除", isCompleted: true };
await CreateTodoTransaction(data, db);
const todo: Todo = await GetTodoTransaction(0, db);
assertEquals(todo.id, 0);
assertEquals(todo.content, "掃除");
assertEquals(todo.isCompleted, true);
});
});
テストの仕様上Todoを作成していますが、詳細のコードは省略しています。
実際に動作するアプリケーションでは、ユースケースを呼び出し、その際にモックではないインフラストラクチャとなるDBと接続可能なコンテキストを注入することでアプリケーションとして動作します。
まとめ
今回はクリーンアーキテクチャでWebサーバーもDBもないTodoアプリを作成しました。
さらに今回はtypescriptの型システムを使用することでドメインを型として定義したり、ユースケースを関数で定義するなどクラスに囚われない形のクリーンアーキテクチャを目指しました。
今回のコードは以下のリポジトリになります。
Discussion