ドメインレイヤーの原則について
学習の目的
この単元を完了すると、次のことができるようになります。
- EAA パターンを使用してドメインパターンの根幹について要約する。
- ドメインレイヤーに属する Apex コード種別を判断する。
- アプリケーションアーキテクチャおよびプラットフォームのどこにドメインレイヤーが適合するかを説明する。
- プラットフォームのベストプラクティスに沿って機能するドメインレイヤーを設計する。
前提条件
ようこそ! このモジュールは、「Apex エンタープライズパターン: サービスレイヤー」モジュールで学習した基本原則とハンズオン Challenge 中に Trailhead Playground で実行した手順に基づいて作成されています。「サービスレイヤー」モジュールでは、サービスレイヤーをアプリケーションのビジネスプロセスをカプセル化する手段として説明しており、特に、一貫性があり、わかりやすい方法でサービスをアプリケーションの他の部分 (Visualforce コントローラー、Apex の一括処理、一般向け API など) に公開する方法に焦点を当てています。
「Apex エンタープライズパターン: サービスレイヤー」モジュールを受講していない場合は、続行する前に受講することをお勧めします。
ドメインレイヤー
ドメイン (ソフトウェア工学) - 「ドメインとは、コンピュータープログラミングの分野で問題を解決するために作られる任意のソフトウェアプログラムに対して、一般的な要求、用語、機能を定義する研究分野である。ドメインエンジニアリングとしても知られる。」— Wikipedia、ドメイン (ソフトウェア工学)
Salesforce プラットフォームエンタープライズパターンでは、ドメインレイヤーは、作成されるカスタムオブジェクト (プロジェクト、請求書など) によって定義されるため、アプリケーションのデータストレージをすばやく作成できます。一方、そうしたオブジェクトの動作、たとえば、検証、計算、データの複雑な操作などはどうなるのでしょうか? Salesforce では、宣言型 (ポイント & クリック) と Apex を使用するプログラミングの両方で動作を表明できます。この組み合わせを有効に利用することが、このプラットフォームのアプリケーション開発者として成功するための鍵となります。誰でもプラットフォームの開発者として成功したいですよね?
ドメインパターン
アプリケーション内のオブジェクトに関連する特定の動作が複雑で Apex コーディングが必要な場合、オブジェクト指向プログラミング (OOP) 手法とドメインモデルパターンを使用したコードの構造化と分離を検討します。
ドメインモデルとは「振る舞いとデータの両方を一体化させたドメインのオブジェクトモデル」であり、「ビジネスロジックにおける最悪の場合とは、そのロジックがとても複雑になっている場合である。ルールやロジックは、多くの異なる事例やさまざまな傾向の振る舞いを記述しているが、このような複雑性に対処できるようにオブジェクトは設計されている。」— Fowler, Martin
サービスレイヤーと同様に、ドメインモデルでは、アプリケーション内でより詳細なレベルのコードのカプセル化と再利用が可能になります。たとえば、複雑な検証、デフォルト設定、複雑な計算や操作に関連するその他のロジックなどのコードです。
この単元では、Apex 固有の視点から見たドメインモデルパターンを取り上げます。ドメインレイヤーコードを各カスタムオブジェクトに明確に関連付けるためのガイドラインを挙げて、アプリケーションコードベース内の有効なレイヤリングと関心の分離 (SOC) を詳細に管理できるようにします。ドメインクラスの構成要素を詳しく見る前に、それらがどこで使用されているかを確認しましょう。
- Apex トリガー - カスタムオブジェクトに対する作成、参照、更新、削除 (CRUD) 操作 (復元を含む) は、ユーザーまたはツールが標準の Salesforce UI またはプラットフォームのいずれかの API を介してやりとりすると発生します。この操作は、そのオブジェクトと操作に対応する適切なドメインクラスコードに転送されます。
- Apex サービス: サービスレイヤーコードは識別しやすくし、また、各操作が 1 つ以上のオブジェクトに関連してドメインクラスを介してやりとりするコードは再利用しやすいものにする必要があります。このアプローチによって、サービスレイヤーのコードでは常に、公開するすべてのビジネスプロセスやタスクをオーケストレーションすることに重点が置かれます。
Apex ドメインクラスは、「Apex エンタープライズパターン: サービスレイヤー」モジュールで紹介した Apex トリガーハンドラーロジックと Apex サービスメソッドコールによって使用されます。この単元では、これら 2 つの領域が、ドメインクラスを介して公開されたロジックをどのように (メソッドをコールする明示的に、またはトリガーハンドラーロジック経由の間接的に) 共有できるかを説明します。
重要: Lightning Web コンポーネントコントローラー、Visualforce コントローラー、Apex 一括処理、および API クラスに関連して開発するコードでは、サービスレイヤー操作を介して公開された機能のみを使用する必要があります。したがって、ドメインレイヤーで記述されたコードは常にサービス層クライアントクラスによって間接的に呼び出されます。ドメインレイヤーコードは通常、アプリケーションの内部的なビジネスロジックのアスペクトです。
デザインの考慮事項
ドメインレイヤーは Apex 内の OOP 機能をアプリケーションが使用できるようにするものですが、それは、アプリケーションのドメインの用語や概念およびプラットフォーム独自のベストプラクティスに沿った方法 (ほとんどは命名規則) で行います。
設計にあたっては、次の点を考慮してください。
一緒にトレイルを進みましょう
エキスパートの説明を見ながらこのステップを実行したい場合は、次の動画をご覧ください。これは「Trail Together」(一緒にトレイル) シリーズの一部です。
サービスからドメインクラスを使用
「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 Id,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
などのメソッドが含まれており、それらの操作が発生するとこのオブジェクトの動作を実装します。
リソース
- GitHub: Apex Enterprise Patterns (Apex エンタープライズパターン)
- ブログ投稿: Martin Fowler: Domain Model (ドメインモデル)
- Wikipedia: ドメイン (ソフトウェア工学)
- Wikipedia: 関心の分離
- Blog 投稿: Salesforce Apex Hours: Apex Enterprise Patterns (Apex エンタープライズパターン)
- 動画: Apex Enterprise Patterns (Apex エンタープライズパターン)
- Blog 投稿: Martin Fowler: Catalog of Patterns of Enterprise Application Architecture (エンタープライズアプリケーションアーキテクチャのパターンのカタログ)