Salesforceで実現するDomain Driven Design(DDD)


参照関係

SalesforceでDDDを実現した場合の参照関係は以下のようになります。

インフラ層(オレンジ)

DBや外部APIに接続する部分になります。
DAOもこちらに該当します。

ドメイン層(グリーン)

システムにおける知識を持ったクラスが該当します。
ドメイン層は二つに分けられます。
 ・ドメインモデル
   ⇒ふるまい・ルールを持ったオブジェクト
 ・ドメインサービス
   ⇒ドメインモデルで表し切れない処理をここに書く
SalesforceにおいてはsObjectが最もドメインモデルに近いかと思います。
なので、ドメインモデルクラスは作りません。
ドメインサービスはsObjectを補助するようなクラスにします。(バリデーションチェックなどが該当)

アプリケーション層(ブルー)

ここではシステムにおけるユースケースに対応するクラスが該当します。

UI/Batch/Trigger/GlobalAPI層(グレー)

ユースケース処理を呼ぶクライアント層になります。
Salesforceでは、UI(Visualforce・LightningComponent等)、Batch、Trigger、GlobalAPI(Apexの外部公開カスタムAPI)が該当します。

高レベルから低レベルへ依存させる

依存は高レベル(より人間に近い動き)から低レベル(より機械に近い動き)に参照させます。
それぞれのパターンから入ってきたユースケースを低次元の処理を使って解決させていきます。

サンプルアプリ

要件

■Contact Create Page
 ・Contactレコードが作成できる
 ・「Account Phone」が同じAccountレコードが既に存在している場合は、そのレコードをContactレコードに紐づける
 ・「Account Phone」が同じAccountレコードがない場合は、作成してそのレコードをContactレコードに紐づける

■Contact Table
 ・Contact Create Pageで作成したレコードが表示される

クラス構成

以下がサンプルアプリのソースコードです。
https://github.com/MASA-JAPAN/Salesforce-DDD-Template

処理概要

■Contact Create Page
LWCフレームワークで開発しております。
コントローラ(dddContactCreateController.cls)は一番上のグレー層に該当します。
こちらからContactApplicationService(アプリケーション層)のユースケース処理(createNewDddContact)を呼びます。

public with sharing class dddContactCreateController {
    public dddContactCreateController() {

    }

    @AuraEnabled
    public static String clickAction( String AccountName, String AccountPhone, String LastName, String FirstName ){
        ContactApplicationService contactAppService = new ContactApplicationService();
        return contactAppService.createNewDddContact(AccountName, AccountPhone, LastName, FirstName);
    }
}

createNewDddContactでは、ドメイン層(sObjectとObjectService層)とインフラ層(DAO)を駆使してユースケースを実現してます。

    public Id createNewDddContact( String AccountName, String AccountPhone, String LastName, String FirstName ){

        //If there is not an Account record, create it.
        AccountService accountService = new AccountService();
        Account targetAccount = new Account( Name = AccountName, Phone = AccountPhone );
        List<Account> sameAccounts = new List<Account>( accountService.getSameAccount(targetAccount) );
        if( sameAccounts.size() == 1){
            targetAccount = sameAccounts[0];
        } else if( sameAccounts.size() == 0 ){
            insert targetAccount;
        } else {
            throw new ContactApplicationServiceException('Phone ' + AccountPhone + ' is duplicated' );
        }

        Contact contact = new Contact(AccountId = targetAccount.Id, LastName = LastName, FirstName = FirstName, LeadSource = 'DDD' );
        insert contact;
        return contact.Id;
    }

さらに詳しくはGithubのソースコードを見て頂ければと思います。

まとめ

ざーっと書いてみました、まだまだブラッシュアップすべき点は満載です!
是非コメント頂けると嬉しいです。