Hangfire のタスク実行時にDependencyInjectionを行う（.NET Core）


## はじめに

[前回の記事](https://qiita.com/yoh1496/items/b8c65243af214ad0e83d)では、プロジェクトを

- app: タスクをキューする側
- server: キューされたタスクを実行する側
- common: タスクを定義したクラスなどを含むクラスライブラリ

という形式に分けることで、今までは式でしか定義できなかったタスクを、
クラス形式で定義し実行できるようにしました。

### 問題点

しかし、現状の構成だと commonクラスにタスクの**実体** を定義して、
それをapp とserver の2プロジェクトで参照する形を取っています。

こうなると問題になってくるのが、

- タスクの内部処理が更新されたらappのビルドし直し
- タスクの内部処理が完成するまでapp, serverの開発／テストがままならない

ということです。

### 解決策

それを解決するのが、`DependencyInjection` ！
[この記事](https://qiita.com/yoh1496/items/45f22bf198b652e18ffe)で説明した、
HostBuilderを使用したHangfire Serverの実装なら、簡単にDependencyInjectionできちゃいます。

## 実装

前回記事の内容をベースに進めていきます。
https://github.com/yoh1496/hangfire-dotnet3.1/tree/tasklib

### タスクが依存するサービスのインタフェース化

タスクが小さなサービスの集まりで構成されているとしましょう。
ここではそのうちの1つ、`AwesomeService` というサービスを実装していきます。

`AwesomeService` は `DoSomething` という関数を持ち、そこで何かすることにします。
まだまだサービス実態が実装できていないので、まずはインタフェースだけ実装します。

```csharp
// common/interfaces/IAwesomeService.cs
namespace common.Interfaces {
    public interface IAwesomeService {
        void DoSomething();
    }
}
```

### タスクの書き換え

では、前回定義したタスクを上記サービスを使用する形式に書き換えていきます。

```csharp
// common/AwesomeTask.cs
public class AwesomeTask {
    private IAwesomeService awesomeService;
    public AwesomeTask(IAwesomeService service) {
        this.awesomeService = service;
    }
    public void Execute(string strText) {
        Console.WriteLine("Super heavy task");
        awesomeService.DoSomething();
        Console.WriteLine($"Done: {strText}");
    }
}
```

コンストラクタで `IAwesomeService` を渡すようにしました。
こうすることで、

```csharp
(new AwesomeTask(service /*←IAwesomeServiceを継承したインスタンス*/)).Execute(strText));
```

として、遠回しにAwesomeTaskに serviceというインスタンスを使用した処理を実行させることができます。

### サービス実体の定義

では、上記で使用する IAwesomeService を継承したクラスを定義しましょう。
しかし、ここではサービスの開発に時間がかかる設定なので、ダミーを定義します。

```csharp
// server/DummyAwesomeService.cs
class DummyAwesomeService : IAwesomeService {
    public void DoSomething() {
        Console.WriteLine("DummyAwesomeService#DoSomething start");
        Thread.Sleep(3000); // wait 3 sec
        Console.WriteLine("DummyAwesomeService#DoSomething end");
    }
}
```

こういったダミーを差し込めるような構成にしておいて、開発のときは

```csharp
var service = new DummyAwesomeService();
(new AwesomeTask(service).Execute(strText));
```

とすることで、開発者はAwesomeTaskの外側の実装に集中することができます。

### DependencyInjection の設定

あとは簡単です。すでにServerはGeneric Hostを使用した実装になっているので（[当該記事](https://qiita.com/yoh1496/items/45f22bf198b652e18ffe))、
DependencyInjectionで何をInjectionするか定義すればよしなにやってくれます。

```csharp
// server/Program.cs
using Microsoft.Extensions.DependencyInjection;
/* 中略 */
var hostBuilder = new HostBuilder()
    .ConfigureServices((context, services) => {
        // 中略
        services.AddHangfireServer(options => {
            options.WorkerCount = 4;
        });
        services.AddTransient<IAwesomeService, DummyAwesomeService>();
    });
/* 中略 */
```

`AddTransient` 関数の `Transient` はDependencyInjectionするときの、
インスタンスの生成タイミングだったり、生存期間を表します。詳しくは公式ドキュメントを読んでください。

https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1#service-lifetimes

## 実行結果

さて、上記の実装で実行してみると・・・

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/328319/f9f874a7-9a8f-a775-7363-a60296030bd5.png)

このように、正しく `DummyAwesomeService` クラスのインスタンスが、
`AwesomeTask` インスタンス生成時に使用され、実行できることがわかります。

### つまり

`DependencyInjection` によって以下の効果がもたらされます。

- キューする側がタスクの実体へリファレンスを持たずにキューすることができるようになる
- serverのソースコード内で動的にタスク実行時に使用するクラスを定義できるので、ダミーへの差し替えが可能


## 終わりに

Hangfire、以前からDependencyInjectionをする機能はあったんですが、

https://docs.hangfire.io/en/latest/background-methods/passing-dependencies.html

やはり、client, serverが同一バイナリになる（ASP.net Coreの裏でタスクも実行するイメージ）構成が
メインでドキュメントが執筆されているのもあり、serverでのDependencyInjectionがやりづらい、
といった背景がありました。

今ではHangfire ServerもGeneric Hostで動作しますし、
Microsoft純正（？）のライブラリでDependencyInjectionし放題になりました。

今回の例ではServiceをDependencyInjectionするのを書きましたが、
どのレイヤーをInjectionするかは設計次第です。開発者のノウハウが生きる部分ですかね。

以上、よきHangfireライフを！


## 追記

今回のコードは↓にあります
https://github.com/yoh1496/hangfire-dotnet3.1/tree/di


## 参考URL

[Hangfireを使ってみる (3)：HangfireでDIコンテナを使ってみる](http://kiyokura.hateblo.jp/entry/2017/08/04/014402)