TypeScript上級

上級 TypeScriptで学ぶクリーンアーキテクチャ|アンチパターン編

導入

クリーンアーキテクチャは、ソフトウェア開発におけるアーキテクチャの設計原則を提供し、メンテナンス性や拡張性を高めるためのフレームワークです。しかし、実際の業務で遭遇するアンチパターンは、この理想から遠ざかる要因となります。本記事では、TypeScriptを用いた具体的なシナリオを通じて、クリーンアーキテクチャの実践における典型的なアンチパターンを取り上げ、その解決策を探ります。

教科書レベルの解説(クリーンアーキテクチャ)

重要な概念の整理

クリーンアーキテクチャは、アプリケーションを層に分け、依存関係を逆転させることで、ビジネスロジックをインフラストラクチャから分離します。これにより、テストが容易になり、変更に強いシステムを構築することが可能です。アーキテクチャの中心には、エンティティ、ユースケース、インターフェースアダプター、フレームワークが存在します。各層は独立しており、特にビジネスロジックを含むユースケース層は、他の層からの影響を受けにくい設計が求められます。

コード例(TypeScript)


class User {
    constructor(public id: number, public name: string) {}
}

interface UserRepository {
    findById(id: number): User | null;
}

class InMemoryUserRepository implements UserRepository {
    private users: User[] = [];

    addUser(user: User) {
        this.users.push(user);
    }

    findById(id: number): User | null {
        return this.users.find(user => user.id === id) || null;
    }
}

コードの行ごとの解説

  1. Userクラスは、ユーザー情報を保持します。
  2. UserRepositoryインターフェースは、ユーザーを取得するためのメソッドを定義しています。
  3. InMemoryUserRepositoryクラスは、ユーザー情報をメモリ内で管理する実装です。
  4. addUserメソッドは、新しいユーザーを追加する機能を提供します。
  5. findByIdメソッドは、指定したIDのユーザーを取得します。

アンチパターン編

クリーンアーキテクチャの実装において、よく見られるアンチパターンの一つは、ビジネスロジックがインフラ層に依存してしまうことです。例えば、InMemoryUserRepositoryクラス内で、ユーザー情報を直接操作するロジックが混在してしまうケースです。このような実装では、テストが困難になり、将来的な変更にも脆弱です。

この問題を解決するためには、ビジネスロジックをユースケース層に移動させ、リポジトリは単にデータの取得・保存を担当するようにすることが重要です。以下は改善されたコード例です。


class UserService {
    constructor(private userRepository: UserRepository) {}

    getUserById(id: number): User | null {
        return this.userRepository.findById(id);
    }
}

ここでは、UserServiceクラスがユースケース層として機能し、リポジトリからのデータ取得を担当します。この設計により、ビジネスロジックがインフラ層に依存することなく、テスト可能な状態を保つことができます。

まとめ

  • クリーンアーキテクチャにおいて、ビジネスロジックがインフラ層に依存することは避けるべきです。
  • ユースケース層を設けることで、テスト容易性と変更への耐性を向上させることができます。
  • 具体的なシナリオに基づいた改善策を適用することで、クリーンなアーキテクチャを実現しましょう。