こんにちは。
テックリードのTerukiです。
Advent CalendarでConsoleAppFrameworkとAWS CDKで爆速バッチ開発という記事を書いたのですが、作者の@neueccさんがこの記事を踏まえてConsoleAppFrameworkのv5に追加機能を入れてくださいました
ConsoleAppFramework v5の思想も非常に共感していたのですが、上記の記事の使い方はv5のコンセプトの真逆を行く使い方だと思っていたのでIssueをオープンするなどもしていませんでした。
私の記事を見て改修を入れてくださったのは非常に感謝です。ありがとうございます!
今後もただの歯医者のシステムを作ってる会社というわけではない、コードレベルでプロダクトと向き合って行く組織にしていきたいなと思っています。
というわけで今回の記事はConsoleAppFrameworkをv5にしてみたというお話です。
アップデート
今回は前回作ったリポジトリがあるので、それをベースにやっていこうと思います。
まずはConsoleAppFramework本体のNuGetパッケージを更新。
サンプルリポジトリなのでアップデート直後のエラーはこのくらいですが、実際のプロダクションコードではもうちょっと多く出ると思います。
基本的に機械的な対応をすればOKなので難しくはありません。
Program.cs
v4では各種DI用NuGetパッケージが入っていたのですが、v5では入っていないので自分でいれる必要があります。
自分で設定をしても良いですが、かなり煩雑になるのでGeneric Hostのデフォルトの処理を使ってしまうのが楽でいいと思います。
というわけで Microsoft.Extensions.Hosting
をインストール。
このNuGetパッケージを入れると HostApplicationBuilder
に ToConsoleAppBuilder
というスーパー便利メソッドが生えるのですぐ使います。
var builder = Host.CreateApplicationBuilder();
// DI登録やConfig処理をする
var app = builder.ToConsoleAppBuilder();
app.Run(args);
ConsoleAppFrameworkネームスペースはProgram.csでglobal usingしてしまうと便利です。
global using ConsoleAppFramework;
コマンド名をケバブケースにされるとちょっと面倒なので用意していただいたケバブケース変換を無効にする設定をします。
[assembly: ConsoleAppFrameworkGeneratorOptions(DisableNamingConversion = true)]
全体像はこんな感じ。
global using ConsoleAppFramework;
using Batch.Filters;
using Microsoft.Extensions.Hosting;
[assembly: ConsoleAppFrameworkGeneratorOptions(DisableNamingConversion = true)]
var builder = Host.CreateApplicationBuilder();
var app = builder.ToConsoleAppBuilder();
app.UseFilter<BatchFilter>(); // 後述
app.Run(args);
少ない。。。
実際はDIをコネコネすると思うのでもうちょっと長くはなると思いますが、ミニマムの初期化処理でこれだけで済むのは非常に良いですね。
これだけなら CreateApplicationBuilder
を使わなくても良いかもと思うかもしれませんが、これは IConfiguration
や ILoggerFactory
の設定をやってくれるので特にデフォルトから弄る必要性がないなら使ってしまっていいかなと思います。
BatchFilter
これはコマンド処理の前後に処理を挟むために使います。
ここが一番変わったのではと思うので先に全体像を載せてしまいます。
using Microsoft.Extensions.Logging;
namespace Batch.Filters;
internal class BatchFilter(ConsoleAppFilter next, ILogger<BatchFilter> Logger) : ConsoleAppFilter(next) {
public override async Task InvokeAsync(ConsoleAppContext context, CancellationToken cancellationToken) {
var timestamp = DateTimeOffset.UtcNow;
var batchName = $"{context.Arguments[0]} {context.Arguments[1]}";
Logger.LogInformation("{name} Batch started. {time}", batchName, timestamp.ToLocalTime());
try {
await Next.InvokeAsync(context, cancellationToken).ConfigureAwait(false);
Logger.LogInformation("{name} Batch completed. {time} Elapsed: {elapsed}", batchName, DateTimeOffset.Now, DateTimeOffset.UtcNow - timestamp);
} catch (Exception e) {
Logger.LogError(e, "{name} Batch failed. {time} Elapsed: {elapsed}", batchName, DateTimeOffset.Now, DateTimeOffset.UtcNow - timestamp);
throw;
}
}
}
contextにLoggerは含まれなくなったのでコンストラクタインジェクションで取ってきます。
contextの Arguments
からコマンドライン引数が取得できるのでそれをLoggerに表示します。
この文章を書いていて思いましたが、バッチ名をログの引数に渡すくらいなら ILogger<T>
の代わりに ILoggerFactory
を取得してバッチ名のILoggerを CreateLogger
するようにしたほうが賢いですね。。
一旦動いてるのでここではそのまま行きます
Batch本体
v5ではConsoleAppBaseクラスがなくなったので一括でコマンドを登録する方法がありません。
その代替手段として、v5.3で RegisterCommands
属性を追加していただいたので早速使います。
using Batch.Attributes;
namespace Batch;
// サブコマンドとして認識させるためにバッチ名をnameofで渡す
[RegisterCommands(nameof(SampleBatch))]
public class SampleBatch {
/// <summary>
/// 19時に実行
/// </summary>
[Batch("0 10 * * *")]
public void Run() {
Console.WriteLine("Hello World!");
}
}
RegisterCommands
はConsoleAppFrameworkネームスペース内にありますが、global usingしているので良い感じです。
Riderだとどうか分かりませんが、私の環境のVisual StudioだとRegisterCommandsの行でAlt+Enterを押してusingを補完させようとしても何故か出てこなかったのでglobal usingしない場合はusingを手書きすることになります
v5.3以前はProgram.csにコマンド登録コードを書かないといけませんでしたが、この属性によって1ファイルの編集で追加することができるようになりました。
ConsoleAppBaseを継承する代わりに属性を付けるだけと思えば手間はv4の時と変わってないので非常に良きです。
これで開発体験を損なわずにv5にすることができました!
おわりに
v4の場合、.NET 6ベースのライブラリが使われているので自社のコードが.NET 8の場合若干チグハグな感じになってしまいずっと気になっていました。
ConsoleAppFrameworkのアップデートで今回の用途に限らずv5の使い勝手が非常に良くなったと思うのでv5への移行がしやすくなったのではないかなと思います。
尚、Oh my teethでは既にv5にアップデートした社内向けのプロダクションコードが既に本番稼働しています
厳密な測定はしていないですが、体感でも起動が早くなったと感じるのでSource Generatorすごいです。
今回分の修正もGitHubのサンプルリポジトリにpushしているので、そちらもよかったら確認してみてください。
では。
Oh my teethについて
Oh my teethでは未来の歯科体験を創るために日々活動しています。
Techチームではより良いユーザー体験を提供するべく、Webフロントエンドからバックエンド、スマホアプリに機械学習モデルなど、さまざまなプロダクトを開発しています。
一緒に未来の歯科体験を創りませんか?興味がある方は是非こちらを確認してください。
カジュアル面談も可能なので気軽に応募してみてください!