AzureFunctionsでDIコンテナを利用する【C#】


AzureFunctionsでDIコンテナを利用する

一応v2のお話です。v1でもあらかた使えるっぽいけど未検証です。

一般的なアプリケーションのDIコンテナでは、アプリ起動時にDIコンテナへ型情報やインスタンス生成方法を詰め込み、必要に応じてインスタンスを取り出して利用します。Azure FunctionsではFunction単位での実行、呼び出しとなるため、Functionが呼び出される前に引数へのバインドを行う必要があります。普通はカスタムバインディングスを作成して実現するのですが、地味に必要なコードが多くて面倒です。

その手間をいい感じに解決してくれるDIコンテナライブラリが「AzureFunctions.Autofac」です。

NuGet Gallery | AzureFunctions.Autofac 3.0.5
https://www.nuget.org/packages/AzureFunctions.Autofac

introtocomputerscience/azure-function-autofac-dependency-injection
https://github.com/introtocomputerscience/azure-function-autofac-dependency-injection

サンプルコード

コメントでいろいろ補足しつつサンプルコードをば。


// DI設定を付与したいFunctionにこの属性を付与する!
// 引数で指定する型を変更することで、複数のDIConfigを使い分けることが出来る!
[DependencyInjectionConfig(typeof(DIConfig))]
public static class GetPlayerVoiceFunction
{
    [FunctionName("GetPlayerVoice")]
    public static IActionResult Run(
        [HttpTrigger("get")] HttpRequest req,
        ILogger log,
        [Inject] IPlayerVoiceService service) // Functionの引数にインジェクトしたい場合、Inject属性が必要だよ!
    {
        // => serviceの実装型は「PlayerVoiceService」です!
        log.LogInformation($"serviceの実装型は「{service.GetType().Name}」です!");

        var playerVoice = service.GetPlayerVoice();

        // => PlayerVoice=スプラトゥーン楽しいいいいいいいいい
        log.LogInformation($"PlayerVoice={playerVoice}");

        return new OkObjectResult(playerVoice);
    }
}

public class DIConfig
{
    public DIConfig(string functionName)
    {
        DependencyInjection.Initialize(builder =>
        {
            // インターフェイスと、それに対応する実装クラスを指定!
            // いろんな設定方法があるので、本家Autofacを参考にしてね!
            // https://github.com/autofac/Autofac#get-started
            builder.RegisterType<InMemoryPlayerVoiceRepository>().As<IPlayeVoiceRepository>();
            builder.RegisterType<PlayerVoiceService>().As<IPlayerVoiceService>();
        }, 
        functionName);
    }
}

public interface IPlayeVoiceRepository
{
    string Get();
}

public class InMemoryPlayerVoiceRepository : IPlayeVoiceRepository
{
    public string Get() => "スプラトゥーン楽しいいいいいいいいい";
}

public interface IPlayerVoiceService
{
    string GetPlayerVoice();
}

public class PlayerVoiceService : IPlayerVoiceService
{
    private readonly IPlayeVoiceRepository repository;

    // PlayerVoiceServiceとInMemoryRepositoryの両方を登録しているので、
    // コンストラクタ引数へInMemoryRepositoryのインスタンスをインジェクトしてくれるよ!
    // Functionの引数じゃないのでInject属性は不要だよ!
    public PlayerVoiceService(IPlayeVoiceRepository repository)
    {
        this.repository = repository;
    }

    public string GetPlayerVoice()
    {
        return repository.Get();
    }
}

便利ですね!

他にも出来ることはたくさんあります。せっかくのオープンソースなのでリポジトリのReadme読んでみたり、Autofacそのものについて調べると幸せになれるかも。

全文はGistにあげてます。
https://gist.github.com/kokeiro001/b7fc8ac08b3c7854f7914f65ee8da9b7

ILoggerの取り回しについて

エンドポイントとなるFunctionメソッドの引数にILoggerを指定すると、Azure Functions側でロガーインスタンスを提供してくれます。標準機能ですね。このロガーに対して書き込みを行うとAzure Table Storageに保存され、ポータル上などから閲覧することができます。

このILoggerを他のクラスでもInjectしたい欲求は非常に強いです。が、私はこの方法分かってません。v1時代やとある時期までのv2ではできたっぽいんですけどね。そのへんのやり取りは公式リポジトリのissueで行われてます。

Add support for injecting the Function provided ILogger into own DI Framework · Issue #2720 · Azure/azure-functions-host
https://github.com/Azure/azure-functions-host/issues/2720

現状の代替案としては、適当なTable Storageなどに保存するオレオレLoggerを作って取り回す感じでしょうか。標準ポータルから見れないのはちと残念ですが。なにかいい方法知ってる人いたら教えて下しあ。

おまけ

ざっくりとした実践としてオレオレライフロガーのリポジトリを貼っときます。

しっかり動かすには対応するIoT機器とか外部サービス設定が必要なので、ソースコード眺める程度にはなりますがドウゾ。

参考

AzureFunctions.Autofacのソースコードを読む場合、先にカスタムバインディングスについて学んでおくと理解がスムーズです。

Azure Functions の Custom Bindings を開発する - Qiita
https://qiita.com/TsuyoshiUshio@github/items/345887a5fa5d59e7444a