Posted at

MicroBatchFrameworkを用いたAzure Functionsの実装例と良いところ紹介


はじめに

MicroBatchFrameworkを用いてAzure Functionsにアプリをデプロイしてみたのでその方法を紹介し、MicroBatchFrameworkの良いところについても記載してみます。


MicroBatchFrameworkとは

詳細は上記参照なのですが、GUIを作成するためのフレームワークとしてPrism等があるように、CUIを作成する際にもフレームワークを用いましょうという感じです。


ざっくりやりたいこと

image.png

上図のように(定期的に)ネット上から情報取得して整形フィルタリングしてネット上のどこかに出力(通知)したいなと考えていました。

情報元の数だけコンソールアプリケーション(バッチスクリプト)を用意してcronにでも仕掛ければ実現できる内容です。


MicroBatchFrameworkを採用した理由

MicroBatchFramework – クラウドネイティブ時代のC#バッチフレームワークから引用


すでにC#にはコマンドライン引数の解析ツールはたくさんあります。とはいえ、そもそもそういうツールを使う時は「コマンドライン引数の解析」がしたいわけではなくて、「パラメータバインディング」をしたいのが一般的と思われます。ということで、『MicroBatchFramework』はウェブフレームワークのようにメソッドを呼び出してくれる仕様にしました。


という点にとても共感しました。これを用いればひとつのコンソールアプリケーションで複数のユースケースを使い分けられると思いました。


ざっくりエントリーポイントの紹介

class Program

{
public static async Task Main(string[] args)
{
await BatchHost
.CreateDefaultBuilder()
.RunBatchEngineAsync(args);
}
}

コンソールアプリのエントリーポイントに上記内容を記述するだけで使えます。


サーバーレスアーキテクチャへの変更

cronのためだけにサーバを起動し続けるのももったいないのでサーバーレスアーキテクチャを採用してみることにしました。

Azure Logic Appsの繰り返しトリガーHttpトリガーのAzure Functionの組み合わせでcronちっくことが実現できました。

デザイナーで見ると

image.png

こんな感じでjsonでコンソールアプリケーションに入力する内容を伝えれば済みます。


Azure Function例

    public static class Function1

{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
await Program.Main(new[] { (string)req.Query["exec"] });

return (ActionResult)new OkObjectResult("OK");
}
}

コンソールアプリケーションといってもエントリーポイントは文字列配列を引数としたstaticメソッドなので外部から呼び出せます。

jsonで取得した情報を文字列配列に置きなおしてキックすれば動きます。

これの何がすごいかって、実装やテストはコンソールアプリケーションで行ってデプロイする際は軽くラップするだけで済むってところです。

いちいちサーバ上やローカルにAzure Functionとして配置して動作確認しなくても良いのです。

さらに良かった点はMicrosoft.Extensions.Logging.ILoggerの使い回し。

Azure Functionのエントリーポイントで渡されるILoggerをMicroBatchFrameworkに設定しなおせばデフォルトではコンソールに出力していた情報もApplication Insights等に出力させることができます。

具体的には以下な感じ


ILoggerProviderを継承したクラスを実装

    public class MyLoggerProvider : ILoggerProvider

{
private ILogger Logger { get; }
public MyLoggerProvider(ILogger _logger) => Logger = _logger;
public ILogger CreateLogger(string categoryName) => Logger;
public void Dispose() { }
}


MyLoggerProviderを生成してコンソールアプリケーションに渡す

    public static class Function1

{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
await Program.MainFunction(new[] { (string)req.Query["exec"] }, new MyLoggerProvider(log));

return (ActionResult)new OkObjectResult("OK");
}
}



コンソールアプリのエントリーポイントとは別に入り口を用意

        public static async Task MainFunction(string[] args, ILoggerProvider _loggerProvider)

{
await BatchHost.CreateDefaultBuilder()
.ConfigureLogging((ILoggingBuilder l) => { l.ClearProviders(); l.AddProvider(_loggerProvider); })
.RunBatchEngineAsync(args);
}

ConfigureLoggingにて(既存のプロバイダを削除して)MyLoggerProviderを追加してあげればAzure Functionで指定しているログ場所へ出力されるようになります。

これでコンソールアプリの時では発生しないAzure Function固有の問題の調査も容易になる思います。


MicroBatchFrameworkを採用することで良くなると思うところ


コンソールアプリ(バッチスクリプト)を使い捨てにしなくて良くなる

業務系だと


  • 黒魔術で書かれたスクリプト

  • ソースがなく挙動がブラックボックスなコンソールアプリ

みたいなのがちらほらあって、いざサーバをリプレースするぞって時に困るんですよね。(動作確認とか作り直しとか)

MicroBatchFrameworkを採用してコンテナ化して運用すれば環境にもよらなくなるしソースもあるしひとつのアプリで済むので管理が楽になると思います。


CUI <-> GUI の垣根がなくなる

例えばGUIで実装していた機能(ユースケース)をCUIとして提供したいというときに


  • GUIでもフレームワーク採用(Prismとか)

  • DDD

  • SOLID

  • オニオンアーキテクチャ(クリーンアーキテクチャ)

を意識してちゃんと機能(ユースケース)単位で注入できるように設計していたならばMicroBatchFrameworkに乗せ換えることは容易だと思うんですよね。

逆も然りなんですけど。

簡単なバッチ処理でもちゃんとユビキタス言語を用い業務知識として蓄えておけば、いざというとき二度手間をしなくても良くなる気がします。


まとめ

CUI側にもフレームワークを導入することでGUIと機能の共通化が行えるのはとても魅力的です。

今後はMicroBatchFramework固有の機能についても紹介できればと思います。