zukucode
主にWEB関連の情報を技術メモとして発信しています。

ASP.NET Core Webとバッチで必要な設定だけを登録する

Web APIとバッチ処理で同じライブラリを使っていると、設定の読み込みもまとめたくなります。

ただし、すべての設定を一括で登録・検証すると、バッチでは使わないWeb専用の設定まで、バッチの起動に必要になることがあります。

今回は、設定を検証する方法は共有し、どの設定を使うかはアプリごとに選ぶ構成を紹介します。

この記事では、公開サイトのURLを使うWeb APIと、処理件数の設定を使うバッチを例にします。それぞれが利用する設定だけを登録する構成にします。

共通の登録処理が不要な依存を作ることがある

たとえば、次の2種類の設定を考えます。

設定Web APIバッチ
公開サイトのURL使うこの例では使わない
1回に整理するデータ件数使わない使う

両方のアプリが同じ「全設定を登録するメソッド」を呼び、すべてを起動時に検証すると、バッチにも公開サイトのURLが必要になります。

使わない設定をダミーの値で埋めると、どの値が本当に必要なのかがわかりにくくなります。必要な設定の不足は検出しつつ、使わない設定を要求しない構成にします。

設定の型に検証条件を書く

以下は.NET 8のASP.NET Coreプロジェクトで使う例です。

AppOptions.cs
using System.ComponentModel.DataAnnotations;

public sealed class PublicSiteOptions
{
    [Required]
    [Url]
    public string BaseUrl { get; set; } = "";
}

public sealed class CleanupOptions
{
    [Range(1, 1000)]
    public int BatchSize { get; set; }
}

Requiredは必須、UrlはURL形式、Rangeは数値の範囲を表します。今回のBatchSizeには既定値を入れていないため、設定がなければ0となり検証に失敗します。

これらの属性を書くだけで、自動的に起動時の検証が行われるわけではありません。次の登録処理と組み合わせます。

設定を型として読み込む基本は、以下の記事を参照してください。

IOptionsでappsettings.jsonの設定値を読み込む方法

検証を含む登録方法を1つにする

OptionsRegistration.cs
using Microsoft.Extensions.DependencyInjection;

public static class OptionsRegistration
{
    public static IServiceCollection AddCheckedOptions<T>(
        this IServiceCollection services,
        string sectionName)
        where T : class
    {
        services.AddOptions<T>()
            .BindConfiguration(sectionName)
            .ValidateDataAnnotations()
            .ValidateOnStart();

        return services;
    }
}

このメソッドは、指定した設定を読み込み、属性による検証を登録し、アプリ起動時に検証するところまでまとめています。

この3つを共通の登録メソッドへまとめると、設定を追加するときに、ある型だけ起動時検証を付け忘れることを減らせます。

ValidateOnStartはホストの開始時に検証を行います。サービスの登録やBuildだけで検証完了と考えず、起動まで確認します。Optionsの検証については、Microsoft Learnの説明も参照してください。

通常のコンソールアプリへ移す場合は、ホストの構成やOptions関連パッケージの参照も必要です。ここではASP.NET Coreの共有フレームワークを利用する例に絞っています。

登録する対象はアプリごとに決める

Web APIでは、公開サイトの設定だけを登録します。

Web側のProgram.csに追加する処理
builder.Services.AddCheckedOptions<PublicSiteOptions>("PublicSite");

バッチ側では、整理処理の設定だけを登録します。

バッチ側の登録処理
builder.Services.AddCheckedOptions<CleanupOptions>("Cleanup");

それぞれの設定ファイルの例です。

Web側のappsettings.json
{
  "PublicSite": {
    "BaseUrl": "https://example.com"
  }
}
バッチ側のappsettings.json
{
  "Cleanup": {
    "BatchSize": 100
  }
}

設定が増えたら、Web用とバッチ用に登録メソッドを用意し、それぞれ必要な型を列挙すると見通しがよくなります。設定ファイルもアプリごとに持つことで、起動に必要な値を確認しやすくなります。

ここでいうアプリの区別は、開発環境と本番環境の区別とは別です。Webの開発環境、Webの本番環境、バッチの開発環境、バッチの本番環境で、必要な設定をそろえます。

型を共有しても値は同じとは限らない

同じストレージ用の設定クラスを使っていても、Webとバッチで接続する先が異なる場合があります。

共有したいのは、項目名と検証条件です。環境ごとの値まで、必ず同じファイルへ置く必要はありません。

反対に、同じキューを使う必要があるなど、アプリ間で値を一致させる条件は、個々の設定検証だけでは確認できない場合があります。デプロイ設定や結合テストで確認します。

URL形式が正しくても、そのURLに接続できるとは限りません。認証情報の権限や、接続先サービスの稼働状況も別の確認です。

起動時の設定検証で何を確認し、接続確認で何を確認するかを分けます。

設定を追加するときの確認項目

設定クラスへ項目を追加するだけでなく、それを使うアプリの登録と設定値も確認します。

確認すること期待する結果
Webの公開URLを未設定にするWebの起動時検証に失敗する
バッチの件数を0にするバッチの起動時検証に失敗する
バッチに公開URLを設定しない使わない設定なので、それを理由には失敗しない
Webにバッチ用の設定を置かない使わない設定なので、それを理由には失敗しない
新しい必須設定を追加する利用するアプリの登録と環境設定も更新する

登録対象から外せば、どんなコードでもその設定を使えなくなるわけではありません。実際にサービスが利用する設定と登録内容が一致しているかも確認します。

アプリが1つなら無理に分けない

Web APIだけで完結しているなら、登録メソッドを用途ごとに細かく分けすぎる必要はありません。

分ける目安は、使わない設定を用意しないと起動できない、別のアプリのための設定変更に巻き込まれる、といった状況です。検証の書き方を共有することと、すべての設定を一括登録することを区別すると、必要な依存関係を保ちやすくなります。


関連記事