詳細: IPlugin の実装をステートレスとして開発する

カテゴリ: 設計、パフォーマンス

潜在的リスク: 高

現象

IPlugin interface を実装するクラス メンバーは、次の問題につながる可能性のある潜在的なスレッドの安全性の問題にさらされます。

  • データの不整合
  • 遅いプラグイン実行

ガイダンス

IPlugin を実装するときに、メンバー フィールドとプロパティを使用せず、ステートレス操作として Execute メソッドを記述します。 各起動状態に関する情報はすべて実行コンテキストを経由してのみアクセスする必要があります。

オーバーロードされたコンストラクターに指定された構成パラメーターから取得したデータでない限り、現在または次回のプラグイン呼び出し中に使用するメンバー フィールドまたはプロパティに実行状態データを格納しようとしないでください。

AppDomain イベントに登録するコードを使用しないでください。 プラグイン ロジックは、AppDomain イベントやプロパティに依存するべきではありません。これは、プラグイン インフラストラクチャの内部実装が、任意の時点での実行動作を変更できるからです。 AppDomain イベントに登録すると、コードがある時点で機能したとしても、エラーを引き起こす可能性があります。

読み取り専用、静的、固定のメンバーは本来スレッド セーフであり、プラグイン クラス内でも確実に使用できます。 次は、スレッド セーフのプラグインを維持する方法についてのいくつかの例です。

一定のフィールド メンバー

public class Valid_ClassConstantMember : IPlugin
{
   public const string validConstantMember = "Plugin registration not valid for {0} message.";

   public void Execute(IServiceProvider serviceProvider)
   {
      var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

      if (context.MessageName.ToLower() != "create")
            throw new InvalidPluginExecutionException(String.Format(Valid_ClassConstantMember.validConstantMember, context.MessageName));
   }
}

割り当てられているか、または設定された構成データをプラグイン クラス コンストラクターに保存

public class Valid_ClassFieldConfigMember : IPlugin
{
   private string validConfigField;

   public Valid_ClassFieldConfigMember(string unsecure, string secure)
   {
      this.validConfigField = String.IsNullOrEmpty(secure)
            ? unsecure
            : secure;
   }

   public void Execute(IServiceProvider serviceProvider)
   {
      if (!String.IsNullOrEmpty(this.validConfigField))
      {
            var message = ValidHelperMethod();
      }
   }

   private string ValidHelperMethod()
   {
      return String.Format("{0} is the config value.", this.validConfigField);
   }
}

ステートレス メソッドの実装

public class Valid_ClassStatelessMethodMember : IPlugin
{
   public void Execute(IServiceProvider serviceProvider)
   {
      var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));

      if (ValidMemberMethod(context))
      {
            //Then continue with execution
      }
   }

   private bool ValidMemberMethod(IPluginExecutionContext context)
   {
      if (context.MessageName.ToLower() == "create")
            return true;
      else
            return false;
   }
}

問題となるパターン

警告

これらのパターンは、回避する必要があります。

プラグイン実行中にプラグイン クラス フィールドのメンバーを割り当てる

public class Violation_ClassAssignFieldMember : IPlugin
{
   //The instance member used in multiple violation patterns
   internal IOrganizationService service = null;
   internal IPluginExecutionContext context = null;

   public void Execute(IServiceProvider serviceProvider)
   {
      this.context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
      var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

      //The violation
      this.service = factory.CreateOrganizationService(this.context.UserId);

      //Invoke another violation in method member
      AccessViolationProperties();
   }

   private void AccessViolationProperties()
   {
      //Accessing the context and service fields exposes this IPlugin implementation to thread-safety issues
      var entity = new Entity("task");
      entity["regardingid"] = new EntityReference(this.context.PrimaryEntityName, this.context.PrimaryEntityId);

      var id = this.service.Create(entity);
   }
}

プラグイン実行中にプラグイン クラス プロパティのメンバーを設定する

public class Violation_ClassAssignPropertyMember : IPlugin
{
   //The instance member used in multiple violation patterns
   internal IOrganizationService Service { get; set; }
   internal IPluginExecutionContext Context { get; set; }

   public void Execute(IServiceProvider serviceProvider)
   {
      this.Context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
      var factory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));

      //The violation
      this.Service = factory.CreateOrganizationService(context.UserId);

      //Invoke another violation in method member
      AccessViolationProperties();
   }

   private void AccessViolationProperties()
   {
      //Accessing the Context and Service properties exposes this IPlugin implementation to thread-safety issues
      var entity = new Entity("task");
      entity["regardingid"] = new EntityReference(this.Context.PrimaryEntityName, this.Context.PrimaryEntityId);

      var id = this.Service.Create(entity);
   }
}

追加情報

Microsoft Dataverse がプラグインのクラスをインスタンス化した後、プラットフォームはパフォーマンス上の理由からそのプラグイン インスタンスをキャッシュします。 Dataverse は、プラグイン インスタンスがキャッシュに格納される時間の長さを管理します。 プラグインの登録プロパティの変更など、特定の操作は、キャッシュを更新するためにプラットフォームへの通知をトリガーします。 これらのシナリオでは、プラグインは再初期化されます。

プラットフォームは、プラグイン クラス インスタンスをキャッシュするため、コンストラクターはプラグイン実行の呼び出しごとに呼び出されるわけではありません。 したがって、IPlugin の実装は、静的な構成データの取得以外に、コンストラクター内の操作のタイミングに依存するべきではありません。

IPlugins がステートレスでなければならないもう 1 つの理由は、複数のシステム スレッドが同じ共有プラグイン インスタンスを同時に実行する可能性があるからです。 このアンチパターンは、IPlugin を実装するクラス メンバーを、データの不整合性やパフォーマンスの問題につながる可能性のある潜在的なスレッド セーフの問題にさらします。

参照

プラグインを記述する

注意

ドキュメントの言語設定についてお聞かせください。 簡単な調査を行います。 (この調査は英語です)

この調査には約 7 分かかります。 個人データは収集されません (プライバシー ステートメント)。