【TypeScript】クリーンアーキテクチャ実装 - 実践ガイド
2024-10-26
2024-10-26
概要
クリーンアーキテクチャは、システムのコアロジックを依存関係から分離し、拡張性とテスト性を重視した設計アプローチです。TypeScript
でクリーンアーキテクチャを実装すると、ビジネスロジックとインフラ層の依存関係を整理しやすく、保守性やコードの見通しが大幅に向上します。本記事では、TypeScript
でのクリーンアーキテクチャの構成と、実際に実装する際のパターンを紹介します。
クリーンアーキテクチャの基本構成
クリーンアーキテクチャは、円状の構造で、依存関係が内側から外側へ向かうように設計されています。内側のコアなロジックが外部に依存せず、逆に外側がコアのロジックに依存する形です。主な構成は以下の4層から成り立っています。
- エンティティ層
ビジネスルールやデータの構造を定義するドメインモデル。アプリケーション固有のロジックや制約を持ちます。 - ユースケース層(アプリケーション層)
ドメインモデルを操作するアプリケーションのユースケース(操作)を定義します。アプリケーションの動作を制御するビジネスロジックを実装します。 - インターフェース層
外部とアプリケーションの橋渡し役。APIやUI、インフラストラクチャなどを取り扱う層で、データの入出力を担当します。 - インフラ層
データベースやファイルシステムなど、インフラ関連の処理を扱う層です。この層はインターフェース層からアクセスされ、直接アプリケーションのコアロジックには関与しません。 この構成により、各層の役割が明確化され、変更に強い柔軟なアーキテクチャが実現します。
TypeScriptでの実装例
TypeScript
でのクリーンアーキテクチャは、クラスやインターフェースを活用して各層の役割を分けることで実現します。以下に、ECサイトの「商品情報取得」を例に各層の実装例を示します。
エンティティ層
エンティティ層では、商品の基本データを定義します。ここでは、商品を表すドメインモデルとしてProduct
クラスを定義します。
// Product.ts
export class Product {
constructor(
public id: number,
public name: string,
public price: number
) {}
}
この層には、ビジネスルールを持つデータ構造が含まれ、アプリケーション層やインフラ層に依存しないシンプルな構成が理想です。
ユースケース層(アプリケーション層)
ユースケース層では、エンティティを利用してアプリケーションの操作を実装します。ここでは商品を取得するGetProductUseCase
を定義します。この層はインターフェースを通して外部(データソース)に依存するため、柔軟にデータ取得の手段を変更できます。
// IProductRepository.ts
export interface IProductRepository {
getProductById(id: number): Promise<Product | null>;
}
// GetProductUseCase.ts
import { Product } from "./Product";
import { IProductRepository } from "./IProductRepository";
export class GetProductUseCase {
constructor(private productRepository: IProductRepository) {}
async execute(id: number): Promise<Product | null> {
return await this.productRepository.getProductById(id);
}
}
このように、リポジトリのインターフェース(IProductRepository
)を通してデータを取得することで、データ取得方法が変更されてもユースケース層は影響を受けません。
インターフェース層
インターフェース層では、ユースケース層とインフラ層を接続します。例えば、HTTPリクエストを受け取り、ユースケースを呼び出して結果を返すコントローラーとしてProductController
を実装します。
// ProductController.ts
import { GetProductUseCase } from "./GetProductUseCase";
import express, { Request, Response } from "express";
export class ProductController {
constructor(private getProductUseCase: GetProductUseCase) {}
async getProduct(req: Request, res: Response): Promise<void> {
const id = parseInt(req.params.id, 10);
const product = await this.getProductUseCase.execute(id);
if (product) {
res.json(product);
} else {
res.status(404).send("Product not found");
}
}
}
ProductController
はGetProductUseCase
に依存しており、依存注入(DI)によってユースケース層とのやり取りを行っています。これにより、ユースケース層の再利用性も高まります。
インフラ層
インフラ層には、実際のデータソースや外部サービスとの接続が含まれます。例えば、ProductRepository
はデータベースから商品データを取得する役割を持ち、リポジトリパターンとしてIProductRepository
を実装します。
// ProductRepository.ts
import { Product } from "./Product";
import { IProductRepository } from "./IProductRepository";
export class ProductRepository implements IProductRepository {
async getProductById(id: number): Promise<Product | null> {
// 仮のデータベース接続
const productData = { id: id, name: "Sample Product", price: 100 };
return new Product(productData.id, productData.name, productData.price);
}
}
このインフラ層は、インターフェース層やユースケース層に提供される実装であり、アプリケーションの他の部分には直接影響を与えません。データソースが変更された場合も、他の層に影響を及ぼさずにインフラ層だけを変更できます。
クリーンアーキテクチャのメリット
クリーンアーキテクチャの最大のメリットは、シ ステムの変更や拡張が必要な際に、影響範囲を最小限に抑えられる点です。
-
テスト性の向上
各層が独立しているため、ユニットテストがしやすく、モックを使用して他の層に依存しないテストが可能です。 -
拡張性の向上
インフラ層(データベースや外部APIなど)が変更されても、他の層への影響が少なく済むため、拡張が容易です。 -
保守性の向上
各層の役割が明確で依存関係が整備されているため、保守や機能追加がしやすくなります。
まとめ
TypeScript
でクリーンアーキテクチャを実装することで、拡張性や保守性に優れたコードを構築できます。エンティティ層、ユースケース層、インターフェース層、インフラ層に分割することで、依存関係が整理され、複雑なソフトウェアの長期的な維持やテストが容易になるでしょう。クリーンアーキテクチャは、規模が拡大しても安定した構造を保てるため、TypeScript
を活用したアプリケーション設計に役立ちます。