web-dev-qa-db-ja.com

Azure WebJobs SDKを使用した依存性注入?

問題は、Azure WebJobs SDKがパブリック静的メソッドのみをジョブエントリポイントとしてサポートすることです。つまり、コンストラクター/プロパティインジェクションを実装する方法はありません。

公式のWebJobs SDKのドキュメント/リソースでこのトピックに関する情報を見つけることができません。私が出会った唯一の解決策は、この投稿で説明されているサービスロケーター(アンチ)パターンに基づいています here

Azure WebJobs SDKに基づいたプロジェクトに「適切な」依存性注入を使用する良い方法はありますか?

60
Milos Mrdovic

Azure WebJobs SDKは、インスタンスメソッドをサポートするようになりました。これをカスタムIJobActivatorと組み合わせると、DIを使用できます。

最初に、お気に入りのDIコンテナを使用してジョブタイプを解決できるカスタムIJobActivatorを作成します。

public class MyActivator : IJobActivator
{
    private readonly IUnityContainer _container;

    public MyActivator(IUnityContainer container)
    {
        _container = container;
    }

    public T CreateInstance<T>()
    {
        return _container.Resolve<T>();
    }
}

カスタムJobHostConfigurationを使用してこのクラスを登録する必要があります。

var config = new JobHostConfiguration
{
    JobActivator = new MyActivator(myContainer)
};
var Host = new JobHost(config);

次に、ジョブのインスタンスメソッドで単純なクラスを使用できます(ここでは、Unityのコンストラクター挿入機能を使用しています)。

public class MyFunctions
{
    private readonly ISomeDependency _dependency;

    public MyFunctions(ISomeDependency dependency)
    {
        _dependency = dependency;
    }

    public Task DoStuffAsync([QueueTrigger("queue")] string message)
    {
        Console.WriteLine("Injected dependency: {0}", _dependency);

        return Task.FromResult(true);
    }
}
89

これが、新しいSDKを使用してスコープを処理する方法です。 Alexander Molenkampの説明に従ってIJobactivatorを使用します。

public class ScopedMessagingProvider : MessagingProvider
{
    private readonly ServiceBusConfiguration _config;
    private readonly Container _container;

    public ScopedMessagingProvider(ServiceBusConfiguration config, Container container)
        : base(config)
    {
        _config = config;
        _container = container;
    }

    public override MessageProcessor CreateMessageProcessor(string entityPath)
    {
        return new CustomMessageProcessor(_config.MessageOptions, _container);
    }

    private class CustomMessageProcessor : MessageProcessor
    {
        private readonly Container _container;

        public CustomMessageProcessor(OnMessageOptions messageOptions, Container container)
            : base(messageOptions)
        {
            _container = container;
        }

        public override Task<bool> BeginProcessingMessageAsync(BrokeredMessage message, CancellationToken cancellationToken)
        {
            _container.BeginExecutionContextScope();
            return base.BeginProcessingMessageAsync(message, cancellationToken);

        }

        public override Task CompleteProcessingMessageAsync(BrokeredMessage message, FunctionResult result, CancellationToken cancellationToken)
        {
            var scope = _container.GetCurrentExecutionContextScope();
            if (scope != null)
            {
                scope.Dispose();
            }

            return base.CompleteProcessingMessageAsync(message, result, cancellationToken);
        }
    }
}

次のようにJobHostConfigurationでカスタムMessagingProviderを使用できます

var serviceBusConfig = new ServiceBusConfiguration
{ 
    ConnectionString = config.ServiceBusConnectionString
};
serviceBusConfig.MessagingProvider = new ScopedMessagingProvider(serviceBusConfig, container);
jobHostConfig.UseServiceBus(serviceBusConfig);
12
Niklas Arbin

own question スコーピングの処理方法について質問した後、私はこの解決策にたどり着きました:これは理想的ではないと思いますが、現時点では他の解決策が見つかりませんでした。

私の例では、ServiceBusTriggerを扱っています。

SimpleInjector を使用しているため、IJobActivatorインターフェイスの実装は次のようになります。

public class SimpleInjectorJobActivator : IJobActivator
{
    private readonly Container _container;

    public SimpleInjectorJobActivator(Container container)
    {
        _container = container;
    }

    public T CreateInstance<T>()
    {
        return (T)_container.GetInstance(typeof(T));
    }
}

ここでは、トリガーWebジョブを扱っています。

したがって、2つの依存関係があります。

  • シングルトン:

    public interface ISingletonDependency { }
    
    public class SingletonDependency : ISingletonDependency { }
    
  • そして、私の機能がトリガーされた時間だけ生きる必要がある別のもの:

    public class ScopedDependency : IScopedDependency, IDisposable
    {
        public void Dispose()
        {
             //Dispose what need to be disposed...
        }
    }
    

そのため、Webjobから独立して実行されるプロセスを使用するために。プロセスをクラスにカプセル化しました:

public interface IBrokeredMessageProcessor
{
    Task ProcessAsync(BrokeredMessage incommingMessage, CancellationToken token);
}

public class BrokeredMessageProcessor : IBrokeredMessageProcessor
{
    private readonly ISingletonDependency _singletonDependency;
    private readonly IScopedDependency _scopedDependency;

    public BrokeredMessageProcessor(ISingletonDependency singletonDependency, IScopedDependency scopedDependency)
    {
        _singletonDependency = singletonDependency;
        _scopedDependency = scopedDependency;
    }

    public async Task ProcessAsync(BrokeredMessage incommingMessage, CancellationToken token)
    {
        ...
    }
}

そのため、今度はwebjobが起動したら、スコープに応じて依存関係を登録する必要があります。

class Program
{
    private static void Main()
    {
        var container = new Container();
        container.Options.DefaultScopedLifestyle = new ExecutionContextScopeLifestyle();
        container.RegisterSingleton<ISingletonDependency, SingletonDependency>();
        container.Register<IScopedDependency, ScopedDependency>(Lifestyle.Scoped);
        container.Register<IBrokeredMessageProcessor, BrokeredMessageProcessor>(Lifestyle.Scoped);
        container.Verify();

        var config = new JobHostConfiguration
        {
            JobActivator = new SimpleInjectorJobActivator(container)
        };

        var servicebusConfig = new ServiceBusConfiguration
        {
            ConnectionString = CloudConfigurationManager.GetSetting("MyServiceBusConnectionString")
        };

        config.UseServiceBus(servicebusConfig);
        var Host = new JobHost(config);
        Host.RunAndBlock();
    }
}

そして、これはトリガーされたジョブです:

  • 依存関係は1つだけです:IoCコンテナー。このクラスはコンポジションルートの一部であるため、大丈夫です。
  • トリガーされた関数へのスコープを処理します。

    public class TriggeredJob
    {
        private readonly Container _container;
    
        public TriggeredJob(Container container)
        {
            _container = container;
        }
    
        public async Task TriggeredFunction([ServiceBusTrigger("queueName")] BrokeredMessage message, CancellationToken token)
        {
            using (var scope = _container.BeginExecutionContextScope())
            {
                var processor = _container.GetInstance<IBrokeredMessageProcessor>();
                await processor.ProcessAsync(message, token);
            }
        }
    }
    
11
Thomas

(選択したIoCコンテナーの用語に応じて)子コンテナー/スコープの概念に依存するいくつかのパターンを使用しました。どれがそれをサポートしているかはわかりませんが、StructureMap 2.6.xとAutoFacがサポートしていると言えます。

アイデアは、入ってくるメッセージごとに子スコープをスピンアップし、そのリクエストに固有のコンテキストを注入し、子スコープからトップレベルのオブジェクトを解決してから、プロセスを実行することです。

AutoFacで表示する一般的なコードを次に示します。回避しようとしているアンチパターンと同様に、コンテナから直接解決しますが、1つの場所に隔離されています。

この場合、ServiceBusTriggerを使用してジョブを起動しますが、何でも構いません-ジョブホストは、さまざまなキュー/プロセスのこれらのリストを潜在的に持つことができます。

public static void ServiceBusRequestHandler([ServiceBusTrigger("queuename")] ServiceBusRequest request)
{
   ProcessMessage(request);
}

このメソッドは、上記のメソッドのすべてのインスタンスによって呼び出されます。子スコープの作成をusingブロックでラップして、確実にクリーンアップします。次に、リクエストごとに変化し、他の依存関係(ユーザー/クライアント情報など)によって使用されるコンテキストを含むオブジェクトが作成され、子コンテナー(この例ではIRequestContext)に注入されます。最後に、作業を行っているコンポーネントは子コンテナから解決されます。

private static void ProcessMessage<T>(T request) where T : IServiceBusRequest
{
    try
    {
        using (var childScope = _container.BeginLifetimeScope())
        {
            // create and inject things that hold the "context" of the message - user ids, etc

            var builder = new ContainerBuilder();
            builder.Register(c => new ServiceRequestContext(request.UserId)).As<IRequestContext>().InstancePerLifetimeScope();
            builder.Update(childScope.ComponentRegistry);

            // resolve the component doing the work from the child container explicitly, so all of its dependencies follow

            var thing = childScope.Resolve<ThingThatDoesStuff>();
            thing.Do(request);
        }
    }
    catch (Exception ex)
    {

    }
}
4
Veatch