JavaScript上級

上級 JavaScriptで学ぶドメイン駆動設計|アンチパターン編

導入

ドメイン駆動設計(DDD)は、ソフトウェア開発においてビジネスの複雑さを扱うための強力なアプローチです。しかし、実際の業務でこれを適用する際には、いくつかのアンチパターンに遭遇することが多いです。本記事では、JavaScriptを用いたドメイン駆動設計における具体的なシチュエーションを考え、ありがちな失敗例や改善ポイントについて詳しく解説します。

教科書レベルの解説(ドメイン駆動設計)

重要な概念の整理

ドメイン駆動設計は、ビジネスのドメインを深く理解し、それに基づいてソフトウェアを設計する手法です。モデルを通じてビジネスのルールやプロセスを反映し、開発チームが共通の言語でコミュニケーションを取ることが重要です。このプロセスには、エンティティ、バリューオブジェクト、アグリゲートなどの概念が含まれます。

コード例(JavaScript)


// 顧客を管理するクラス
class Customer {
    constructor(name, email) {
        this.name = name;
        this.email = email;
    }

    updateEmail(newEmail) {
        this.email = newEmail;
    }
}

// 顧客管理サービス
class CustomerService {
    constructor() {
        this.customers = [];
    }

    addCustomer(customer) {
        this.customers.push(customer);
    }

    findCustomerByEmail(email) {
        return this.customers.find(customer => customer.email === email);
    }
}

コードの行ごとの解説

  1. クラスCustomerは顧客情報を保持し、メールアドレスの更新を行うメソッドを持っています。
  2. クラスCustomerServiceは顧客の追加と検索を行うサービスクラスです。
  3. addCustomerメソッドで新しい顧客を管理リストに追加します。
  4. findCustomerByEmailメソッドで、指定されたメールアドレスを持つ顧客を検索します。

アンチパターン編

ドメイン駆動設計を実践する際に見られる一般的なアンチパターンの一つに、ビジネスロジックをサービスクラスに過度に集中させることがあります。以下の例では、顧客のメールアドレス更新のロジックがCustomerServiceに含まれてしまっています。


// アンチパターンの例
class CustomerService {
    constructor() {
        this.customers = [];
    }

    updateCustomerEmail(oldEmail, newEmail) {
        const customer = this.findCustomerByEmail(oldEmail);
        if (customer) {
            customer.updateEmail(newEmail);
        }
    }
}

このコードの問題点は、CustomerServiceが顧客のメールアドレス更新というビジネスロジックを直接扱っていることです。これにより、サービスクラスが肥大化し、責任が不明確になります。理想的には、メールアドレスの更新はCustomerクラス内のメソッドとして保持し、サービスクラスはそのメソッドを呼び出すだけにとどめるべきです。


// 改善されたコード
class Customer {
    constructor(name, email) {
        this.name = name;
        this.email = email;
    }

    updateEmail(newEmail) {
        if (this.isValidEmail(newEmail)) {
            this.email = newEmail;
        }
    }

    isValidEmail(email) {
        // メールアドレスのバリデーションロジック
        return /\S+@\S+\.\S+/.test(email);
    }
}

// サービスクラスはシンプルに
class CustomerService {
    constructor() {
        this.customers = [];
    }

    addCustomer(customer) {
        this.customers.push(customer);
    }

    findCustomerByEmail(email) {
        return this.customers.find(customer => customer.email === email);
    }
}

このように、ビジネスロジックを適切なクラスに分散させることで、コードの可読性と保守性が向上します。また、テストもしやすくなり、将来的な変更にも対応しやすくなります。

まとめ

  • ビジネスロジックは適切なクラスに分散させるべきです。
  • サービスクラスは、ビジネスロジックを直接扱わず、他のクラスのメソッドを呼び出す役割に徹することが望ましいです。
  • このアプローチは、他のプログラミング言語でも同様に適用可能です。