進行状況の追跡を始めよう
Trailhead のホーム
Trailhead のホーム

ドメインレイヤの原則について

学習の目的

この単元を完了すると、次のことができるようになります。

  • Martin Fowler の EAA パターンで紹介されているドメインパターンの根幹について要約する。
  • ドメインレイヤに属する Apex コード種別を判断する。
  • アプリケーションアーキテクチャおよびプラットフォームのどこにドメインレイヤが適合するかを説明する。
  • プラットフォームのベストプラクティスに沿って機能するドメインレイヤを設計する。

概要

重要: このモジュールを開始する前に、「Apex エンタープライズパターン: サービスレイヤ」モジュールを修了してください。「サービスレイヤ」モジュールでは、サービスレイヤをアプリケーションのビジネスプロセスをカプセル化する手段として説明しており、特に、一貫性があり、わかりやすく役に立つ方法でサービスをアプリケーションの他の部分 (Visualforce コントローラ、Apex の一括処理、一般向け API など) に公開する方法に焦点を当てています。

この単元では、ドメインレイヤと呼ばれるアプリケーション内のレイヤについて説明します。

[ドメイン (ソフトウェアエンジニアリング)](http://en.wikipedia.org/wiki/Domain_(software_engineering) - 「ドメインとは、作成するソフトウェアプログラムで共通する要件、用語、機能のセットを定義して、コンピュータプログラミングの領域における問題を解決する研究分野であり、ドメインエンジニアリングと呼ばれる」

Force.com では、ドメインレイヤは、作成されるカスタムオブジェクト (プロジェクト、請求書など) によって定義され、アプリケーションのデータストレージをすばやく作成できるようにします。一方、そうしたオブジェクトの動作、たとえば、検証、計算、データの複雑な操作などはどうなるのでしょうか? Force.com では、宣言型 (ポイント & クリック) と Apex を使用するプログラミングの両方で動作を表明できます。この組み合わせを有効に利用することが、このプラットフォームのアプリケーション開発者として成功するための鍵となります。誰でもプラットフォームの開発者として成功したいですよね?

ドメインパターン

アプリケーション内のオブジェクトに関連する特定の動作が複雑で Apex コーディングが必要な場合、オブジェクト指向プログラミング (OOP) 手法とドメインモデルパターンを使用したコードの構造化と分離を検討します。

ドメインモデルとは「振る舞いとデータの両方を一体化させたドメインのオブジェクトモデル」であり、ルールやロジックは、多くの異なる事例やさまざまな傾向の振る舞いを記述しているが、このような複雑性に対処できるようにオブジェクトは設計されている。」Martin Fowler、『エンタープライズアプリケーションアーキテクチャパターン』

サービスレイヤと同様に、ドメインモデルでは、アプリケーション内でより詳細なレベルのコードのカプセル化と再利用が可能になります。たとえば、複雑な検証、デフォルト設定、複雑な計算や操作に関連するその他のロジックなどのコードです。

この単元では、Force.com の視点から見たドメインモデルパターンを取り上げます。ドメインレイヤコードを各カスタムオブジェクトに明確に関連付けるためのガイドラインを挙げて、アプリケーションコードベース内の有効なレイヤリングと関心の分離 (SOC) を詳細に管理できるようにします。ドメインクラスの構成要素を詳しく見る前に、それらがどこで使用されているかを確認しましょう。

  • Apex トリガ - カスタムオブジェクトに対する作成、参照、更新、削除 (CRUD) 操作 (復元を含む) は、ユーザまたはツールが標準の Salesforce UI またはプラットフォームのいずれかの API を介してやりとりすると発生します。この操作は、そのオブジェクトと操作に対応する適切なドメインクラスコードに転送されます。

  • Apex サービス - サービスレイヤコードは識別しやすくし、また、各操作が 1 つ以上のオブジェクトに関連してドメインクラスを介してやりとりするコードは再利用しやすいものにする必要があります。このアプローチによって、サービスレイヤのコードでは常に、公開するすべてのビジネスプロセスやタスクをオーケストレーションすることに重点が置かれます。

Apex ドメインクラスは、前のモジュールで紹介した Apex トリガハンドラと Apex サービスメソッドコールによって使用されます。この単元では、これら 2 つの領域が、ドメインクラスを介して公開されたロジックをどのように (メソッドをコールする明示的に、またはトリガハンドラ経由の間接的に) 共有できるかを説明します。

ドメインロジックの共有

重要: Visualforce コントローラ、Apex 一括処理、および API クラスに関連して開発するコードでは、サービスレイヤ操作を介して公開された機能のみを使用することが推奨されます。したがって、ドメインレイヤで記述されたコードは常に間接的に呼び出されます。ドメインレイヤコードは通常、アプリケーションの内部的なビジネスロジックのアスペクトです。

デザインの考慮事項

基本的に、ドメインレイヤは Apex 内の OOP 機能をアプリケーションが使用できるようにするものですが、それは、アプリケーションのドメインの用語や概念およびプラットフォーム独自のベストプラクティスに沿った方法 (ほとんどは命名規則) で行います。

  • 一括処理化 - サービスレイヤと同様に、一括処理化は Force.com での重要な設計上の考慮事項であり、それに基づいて、ドメインレイヤには、ドメインクラスのコンストラクタ、パラメータ、メソッド、プロパティはデータを 1 つのインスタンスではなくリストとして扱うという設計ガイドラインがあります。このアプローチによって、この重要なベストプラクティスは明らかに促進されており、下位レイヤへと広がっています。サービスを一括処理化しても、他のアプリケーションコードがそれをサポートしていなければ意味がないということです。

  • コンテインメントによる拡張 - ドメインロジックはデータと動作の両方をカプセル化する必要があります。Apex は、データを sObject として表現します。sObject は拡張できませんが、適切な動作コード (メソッド、プロパティなど) と一緒に、データを補完する別のクラスでラップまたは包含できます。

  • 命名規則 - ドメインデータは、一括処理化規則に沿ったデータセットまたはリストであるため、ドメインクラスではその名前にこれを反映することが理想的です (たとえば、public class Invoices など)。

  • OOP - Force.com では、Java のような OOP 言語でクラスが行うのと同じ方法で、あるカスタムオブジェクトが別のオブジェクトを拡張することはできません。そのため、往々にして同じようなアスペクト、項目のセット、リレーションを共有するカスタムオブジェクトが沢山作られてしまいます。こうしたオブジェクトのドメインクラスを定義するときには、継承またはインターフェースを使用して共通の動作アスペクトを共有基本クラスまたはインターフェースに抽象化し、こうしたオブジェクト全体に適用されるロジックの作成と再利用を可能にすることを検討してください。この単元では、OOP のアスペクトをいくつか使用します。継承やメソッド上書きのようないくつかの基本事項に習熟していると理想的です。

  • セキュリティ - ドメイン Apex クラスでは with sharing または without sharing キーワードの使用を避け、コール元のコンテキストでこのアスペクトが駆動されるようにします。通常、これはサービスクラスロジックであり、設計ガイドラインに従い、with sharing を指定する必要があります。

  • 関心の分離 - 検証、デフォルト設定、計算など、共通する種類のドメインロジックを配置する場所に一貫性を持たせます。これをデータベースおよびトリガイベントのみに関連する他のドメインロジックと混在させることは可能ですが、このロジックをドメインクラスのさまざまなメソッドに分割することもできます。このコードを公開して、データベース操作による駆動ではないコンテキスト (検証を提供するサービスやデフォルト設定のみを行う動作など) でアクセス可能にすると便利かどうかを考慮します。

サービスからドメインクラスを使用

「Apex エンタープライズパターン: サービスレイヤ」モジュールで紹介されている OpportunitiesService で、applyDiscounts メソッドの動作の一部をさらにドメインクラスメソッドにリファクタリングする方法を見てみましょう。

次のコードは、サービスメソッド applyDiscounts の新しい実装を示しています。今回は、この単元でこれから作成する新しい Opportunities クラスを使用します。このクラスは、独自の applyDiscount メソッドを介して割引計算を実行するロジックをカプセル化します。

public static void applyDiscounts(Set<Id> opportunityIds, Decimal discountPercentage) {

    // Unit of Work
    // …

    // Validate parameters
    // ...

    // Construct Opportunities domain class
    Opportunities opportunities =
        new Opportunities(
            [select Amount, (select UnitPrice from OpportunityLineItems)
            from Opportunity where Id in :opportunityIds]);

    // Apply discount via domain class behavior
    opportunities.applyDiscount(discountPercentage, uow);

    // Commit updates to opportunities     
    uow.commitWork();                      

}

トリガからドメインクラスを使用

別のエントリポイントも検討しましょう。上記の図が示すように、ユーザは Salesforce UI か、DML または Salesforce API を介してレコードを操作するコードを使用して Apex トリガを呼び出します。

あるドメインオブジェクトについてすべてのロジックと動作をカプセル化するというドメインクラスの役割を考えると、Apex トリガコールも適切なクラスメソッドにも転送されるようにする必要があります。

Apex トリガに関しては多くのパターンが新たに登場しているため、トリガのロジックは最小限に保つことをお勧めします。Apex トリガはクラスではなく、コードの分離や、継承のような OO 原則の使用の手段は限られています。やや複雑なロジックにさえ適しているとは言えません。

次の Apex トリガコードの実行には、Apex エンタープライズパターンオープンソースライブラリに含まれる fflib_SObjectDomain クラスの使用が前提となります。このクラスは、アプリケーション内のすべてのドメインクラスの基本クラスを形成します。これについては、次の単元で詳しく説明します。

静的メソッド fflib_SObjectDomain.triggerHandler では、システムの Trigger コンテキスト変数 (Trigger.isAfter、Trigger.isBefore、Trigger.isInsert、Trigger.new など) を入力することで Opportunities クラスの該当するメソッドがコールされます。このパターンを使用して Apex トリガに追加されるコードは、意図的に非常に軽量になっています。

trigger OpportunitiesTrigger on Opportunity (
  after delete, after insert, after update, after undelete, before delete, before insert, before update) {

   // Creates Domain class instance and calls appropriate methods
   fflib_SObjectDomain.triggerHandler(Opportunities.class);
}

上記のコードで参照される Opportunities クラスには、onBeforeInsert や onAfterUpdate などのメソッドが含まれており、それらの操作が発生するとこのオブジェクトの動作を実装します。

リソース