パッケージとは、アップグレードとメンテナンスを容易にするために分離および整理されたメタデータのグループです。組織ベースの開発モデルでは、アプリケーションに属するメタデータはSalesforce組織全体に分散されます。この相互連動関係により、組織およびその組織内のアプリケーションのメンテナンスが難しくなります。または、パッケージベースの開発モデルでは、アプリケーションまたはライブラリのメタデータはパッケージコンテナ、または場合によっては一連のパッケージコンテナに属します。この分離と整理により、メタデータの再利用が向上し、Salesforceアプリケーションへのアップグレードが迅速かつ予測しやすくなります。

ただし、パッケージベースの配信モデルの導入は、メタデータを単一パッケージに整理することと同じぐらいシンプルではありません。おそらく、相互に関連するコンポーネントを含む複数のパッケージがあります。パッケージの連動関係は、特定のパッケージ内部のコンポーネントがそのパッケージの外部にあるコンポーネントまたは設定を参照する場合に作成されます。連動関係の形式は、Apexクラスおよびトリガだけでなく、アクション、Lightningページ、フローの場合もあります。この記事では、ロック解除済みパッケージ間の連動関係を処理する方法に焦点を合わせます。

複雑な連動関係を持つロック解除済みパッケージを構築するための一般的なアプローチは2つあります。組織連動ロック解除済みパッケージ(インストールする組織に密結合している)を構築するか疎結合のロック解除済みパッケージを構築し、連動関係を管理するように設計します。ここでは2つ目のオプションについて説明し、パッケージの連動関係と関連する責任を回避するための非常に便利なテクニックである依存性の注入の設計パターンを使用して疎結合のロック解除済みパッケージを設計および構築するための方法を紹介します。

連動関係の設計の5つのアンチパターンとその回避方法

パッケージ化の主な目的の1つは、リリースをより簡単にし、標準化し、反復可能にすることです。メタデータ間の連動関係を管理し、結合を少なくすることが、この目的を達成するにあたって重要です。

このセクションでは、アーキテクトが目的とするビジョンを達成するのを妨げる一般的なトラップについて説明します。さらに重要な、これらの問題を解決するためのApexエンタープライズパターンと依存性の注入テクニックを適用する方法についても説明します。

アンチパターン1:パッケージデータを使用することでサイロ(および技術的負債)が生じる

説明:ロック解除済みパッケージは、異なる製品所有者がSalesforce組織内で別のアプリケーションの設計および開発を管理できるようにする便利なツールです。しかし、各パッケージを完全に独立したテリトリーであるかのように扱い、パッケージが同じSalesforce組織内の他のアプリケーションや共有リソースとどのように対話するかを考慮しないパッケージをチームが開発および設計できるようにすることは適切ではありません。

例:たとえば、自動化プロセスを設計する際のベストプラクティスは、”オブジェクトごとに1つの自動化ツール“です。多くの場合、異なるアプリケーションチームが、独自の要件範囲にもとづいて自動化ツールを独自に選択しています。その結果、取引先、取引先責任者、ケース、商談などの主要オブジェクトで自動化が混在し、場合によっては競合します。この種類の技術的負債は、大きなパフォーマンスの問題、さらにはランタイムエラーを引き起こす可能性があります。

回避方法:トリガハンドラーフレームワークは、”オブジェクトあたり1つの自動化ツール”というルールを適用するための優れた方法です。しかし、自動化ロジックを共通オブジェクト、たとえば取引先オブジェクトに導入する必要があるパッケージが複数存在する場合はどうなるでしょうか。解決策の1つは、トリガハンドラー用のコードでの依存性の注入の使用です。このアプローチを使用すると、トリガハンドラーは、インジェクターサービスを呼び出して適切な自動化設定をロードします。実行時、インジェクターサービスに渡されるカスタムメタデータまたはその他のランタイム設定データは、異なるパッケージに含まれている自動化ロジックがどのように関与するかを制御します。フローの起動にもApexを使用できるので、このアプローチはフローにも拡張できます。

トリガ注入パターンのパッケージ戦略は、ベースTriggerinjectorクラスの拡張と同じぐらい容易なものにすることができます。(実装の詳細については、force-di-trigger-demo例を参照してください。)



アンチパターン2:使いやすいサービスではなくモノリスを設計する

説明:関心の分離は、多くのアーキテクトが採用している重要な設計原則です。ロック解除済みのパッケージは、再利用可能なサービスと効率性向上をSalesforce組織およびコードベースの設計に組み込むための優れた方法です。ただし、非常に柔軟性に欠け、拡張が困難なサービスは、将来の連動関係パッケージにとって技術的負債となる可能性があります。チームが適切に設計されていないサービスをバイパスし、冗長ロジックをパッケージに組み込もうとし始める場合もありますが、関心の適切な分離が完全に台無しになります。

例:取引先データを読み取るベースパッケージ内のサービスがあるとします。サービス内のSOQLステートメントには、パッケージの作成時のデータモデルにもとづく定義済みの項目リストがあります。異なるパッケージによって取引先オブジェクトにその他の項目を追加する際、共有サービスも新規項目を参照するように更新する必要があります。または、チームがベースパッケージを更新できない場合は、新規パッケージで取引先データを読み取るための独自のサービスを作成する必要があります。

回避方法:ハードコード化されたロジックまたはデータモデル情報によってサービスを実装しないようにします。上記のシナリオは、Apexエンタープライズパターン – セレクタレイヤ実装の動的項目注入の概念を適用することで、容易に処理できます。フィールドのリストをクエリにハードコード化する代わりに、セレクタクラスで、分離されたパッケージで設定できるカスタムメタデータまたはランタイム設定データを使用して項目リストを構築する必要があります。

Easy Spacesサンプルアプリケーションは、セレクタパターンでカスタムメタデータタイプを使用するポリモーフィックサービス実装の適切な例です。(詳細については、ESBaseCodeLWCパッケージで定義されているクラスおよびメタデータを参照してください。)

アンチパターン3:適切なコードを実装するのではなく実装を終わらせるためのコードを記述する

説明:アプリケーションの設計がどれほど適切でも、時間の経過とともにアプリケーションを拡張および変更する必要があります。そうしなければ停止してしまいます。アプリケーションを変更する必要があるときに、既存のコードへの影響を可能な限り少なくするには、アプリケーションパッケージをどのように構築すればよいでしょうか。変更とパッケージロジックの拡張を適切に行えないようにするための1つの方法は、現在の実装の詳細または要件と密結合したコードを記述することです。これは、コードが柔軟性に欠け、拡張が困難という点で、アンチパターン2と非常に似ています。

例:カスタムLoggerオブジェクトとプラットフォームイベントベースのログの2つの具象実装を含む抽象クラスLoggerを想像してみてください。特定の実装に固有のコードの記述は、以下のスニペットのようになります。

SysLog_Event__e eventlog = new SysLog_Event__e();

eventlog.payload__c = ‘some message’;

EventBus.publish(eventlog);

変数eventlogSysLog_Event__e型で宣言することは、ログ機能の具象実装であり、コードをこの特定の実装に密結合します。これにより、このコードを含むパッケージとSysLog_Event__eプラットフォームイベントとの間に連動関係が発生します。ロガー機能のいずれかの具象実装が変更されると、Loggerクラスでの手直しが必要になり、パッケージの追加更新が強制される場合があります。

回避方法:幸いなことに、この種類の状況に対する設計原則があります。

  1. アプリケーションの可変要素を特定し、それらを変化しない要素から切り離します。
  2. 実装ではなく、抽象化を目的としてプログラミングします。

上記の例では、SysLog_Event__eオブジェクトの呼び出し側がその作成と有効期間を制御します。制御は、オブジェクト作成を別のクラスへ委任することによって反転させることができます。つまり、以下に示すように、連動関係の作成制御を呼び出し側から別のクラスへと反転させます。連動関係のインスタンス化はハードコード化されるのではなく、分離され、実行時に割り当てられます。この実装は、実際のランタイムオブジェクトがパッケージにロックされないように抽象化を目的としてプログラミングすることにより、ポリモーフィズムを活用します。

Logger logger = LoggerFactory.newInstance(EventLogger.class);

logger.log(‘some message’);

実際に、これらの設計原則をソリューションで適用して、パッケージとパッケージが一部の操作を実行するために連動する可能性がある組織内のApexクラスとの間の密結合を解消することができます。ソリューションでは、インジェクタークラスを使用し、異なる種類の制御を反転させて疎結合を実現することで、実装クラスを実行時に動的にロードして返します。インジェクターは、特定の実装クラスへのコンパイル時参照を持たないため、すべての実装クラス参照がパッケージから削除されます。

制御の反転(IoC)を使用してパッケージと実装の結合を少なくする方法を実際に示すために、ログシナリオを拡張してみましょう。この図では、Loggerインターフェースの具象クラスは、カスタムメタデータで定義され、package-di内のサービスインジェクターによって動的にロードされます。package-a内のインターフェースコントラクトのスタブ実装は、インジェクターを使用して具象実装を実行時にロードします。Apexスタブは、実際のエンティティの抽象化で、エンティティと連動関係との間の疎結合を実現します。package-bは、クライアントサービスおよびLogger具象実装クラスを規定するカスタムメタデータレコード定義から構成されます。



IoC原則は、テスト、メンテナンス、拡張が容易な疎結合パッケージの設計に役立ちます。

アンチパターン4:パッケージの境界を越える場合にユニットテストを正しく行わない

説明:別のオブジェクトの内部状態または別のパッケージからの実装に連動するコードをテストしていると想像してみてください。テストメソッドを機能させるために、そのメソッドから制御側パッケージまたはその連動関係を呼び出すことができますが、このアプローチにはいくつかの問題があります。最初の問題は、連動関係がまだ最終決定されていない、あるいは存在しない可能性があることです。次に、テストメソッドは連動関係に密結合しているため、後者を変更すると、(少なくとも)すべての連動関係の再実行、または(最悪のシナリオでは)連動するユニットテストメソッドおよびパッケージの更新が必要になります。これらはいずれも最適なソリューションではありません。

例:このシナリオは、テストメソッドが組織内または別のパッケージから既存のクラスを参照する場合に発生することがあります。これにより、開発者は、テストメソッドをコンパイルするだけのために、ロック解除済みのパッケージの定義に追加の連動関係を列挙せざるを得なくなることがあります。

回避方法:可能な限り多く、テストを制御側のパッケージ内の連動関係から分離して、テストを常に実行しやすくします。連動関係を分離するには、これらの呼び出しに対してインターフェースを作成して、複数の実装を含めることができるようにします。テストを実行しやすくする1つの実装を開発用に含め、実際のオブジェクトを使用する別の実装を本番環境用に含めることができます。

本番環境用のクラスの機能をシミュレートするために使用できる基本タイプのオブジェクトが2つあります。スタブまたはモックオブジェクトです。モックオブジェクトを作成して、テスト対象のコードを組織内のテスト対象ではない他のコード、たとえば、テスト時にアクティブなソース以外の他のソースからの連動関係またはコードから分離します。このアプローチの詳細については、Use Mocks and Stub Objects(モックおよびスタブオブジェクトの使用)Trailheadモジュールを参照してください。

アンチパターン5:イベント処理を通じて密結合パッケージを作成する

説明:1つのパッケージが別のパッケージで発生するイベントの通知を受ける必要があるケースは極めて一般的です。この種類の通信は、多くの場合、パッケージ間の何らかの種類のAPI呼び出しによって実装されます。このアプローチの短所は、互いに開示される特定のAPIについて認識する必要がある密結合のパッケージが作成される可能性がある点です。1つのパッケージでサービスパラメーターまたは実装を変更する場合、呼び出し側のパッケージで変更の連鎖が発生する可能性があります。

例:複数パッケージの組織でリードがパッケージAによってコンバージョンされるときに以下の操作がトリガされると想定しましょう。

  1. パッケージB内のサービスを呼び出して既存の取引先を更新する
  2. パッケージC内のメールサービスを呼び出してセールスマネージャーに通知する
  3. 別のパッケージ内のサービスを呼び出して他の操作を行う



これらのサービス呼び出しはさまざまな方法で調整できますが、一般に、APIベースのアプローチは密結合のパッケージおよび統合につながり、メンテナンスが難しくなります。

回避方法:依存性の注入(DI)ベースのイベントサブスクリプションパターンを使用すると、以下の図に示すように疎結合を維持しながら、パッケージ間の通信を実現できます。パッケージBおよびCは、イベントを処理するためにパッケージ内のどのクラスを呼び出すかなど、イベントサブスクリプションカスタムメタデータでエントリを追加することで、Convert Leadイベントにサブスクライブします。Convert Leadイベントが発生すると、パッケージAはイベントサブスクリプションメタデータレコードをクエリして、このイベントにサブスクライブ済みのパッケージを見つけ出し、カスタムメタデータエントリにもとづいてクラス呼び出しを調整します。このアプローチでは、イベントハンドラークラスおよびパッケージは密結合されません。



このDIベースのアプローチを使用すると、一般に、汎用性が向上し、パッケージ間の統合を維持しやすくなります。

上記のソリューションは、以下のアーティファクトから構成されます。

  • コアライブラリ/パッケージ:
  • インターフェース:IEventConsumer
  • カスタムメタデータタイプ:EventConsumerSubscription__mdt
  • Apexクラス:イベントサブスクリプションメタデータレコードをクエリし、実装クラスの実行を調整するためのEventOrchestrator
  • パッケージBおよびC:
  • Apexクラス:EventConsumerはイベント処理用にIEventsConsumerを実装
  • カスタムメタデータレコードはイベント処理用の実装クラスを定義

まとめ

ロック解除済みのパッケージなど、最新のアプリケーション開発戦略の導入は、オールオアナッシングの命題ではありません。組織がパッケージ化されたメタデータとパッケージ化されないメタデータを組み合わせることは一般的です。疎結合のパッケージの構築は、チームが変更を定期的に、より柔軟にリリースするのに役立ちます。ロック解除済みのパッケージに取り組み始めたばかりであれば、多くの場合、適切に定義された自己完結型のものをベースにすると、最初のパッケージを簡単に構築できます。再利用可能なApexコードのライブラリをパッケージに変換する予定であろうと、新しいアプリケーションをパッケージとして構築する予定であろうと、この記事のヒントを活用して、技術的負債を招く一般的な落とし穴を回避してください。

リソース

著者紹介



Abi Ashiruは、Salesforceでテクニカルアーキテクトを務め、テクノロジーによるビジネスイノベーションおよび成長を支援しています。Salesforceのクラウドを対象とした拡張性の高いソリューションの構築に幅広く取り組んできました。Salesforceでソリューションを構築していないときは、第4次産業革命技術の調査を楽しんでいます。



Ivan Yeungは、SalesforceのSuccessアーキテクトです。革新的なお客様がSalesforceプラットフォームでエンタープライズクラスのアプリケーションを設計、構築、管理できるよう支援しています。NLP AI、ブロックチェーンなどの最先端テクノロジーをSalesforceソリューションに応用することに情熱を注いでいます。

オリジナル記事は下記となります。

5 Anti-Patterns In Package Dependency Design and How to Avoid Them (Salesforceアーキテクト) 

Get the latest Salesforce Developer blog posts and podcast episodes via Slack or RSS.

(英語のみです)

Add to Slack Subscribe to RSS