第69章:設定値(appsettings.json)とドメインの距離 📏🧠
この章は一言でいうと… 「ドメインは “設定ファイルの存在” を知らない方が強い」 です 💪✨ (設定を読んでいい場所・ダメな場所をスッキリ分けます😊)
今日のゴール 🎯😊
appsettings.jsonの値を、ドメイン層に漏らさないようにできる 🙆♀️- 「それって設定?それともビジネスルール?」を見分けられる 👀✨
- DI(
IOptions<T>)で “安全に設定を使う型” が身につく 🧩✅

まず結論:ドメイン層は appsettings を読まない 📵📦
ドメイン層は「業務ルールの中心」なので、ここに…
IConfigurationIOptions<>Microsoft.Extensions.*appsettings.jsonのキー名(例:"TaxRate": 0.1)
が登場したら、距離が近すぎです ⚠️😵💫
理由はカンタン👇 ドメインが設定に依存すると、将来こうなりがちです:
- 設定キー名を変えたらドメインが壊れる 😱
- テストが面倒(設定読み込みが必要)🧪💦
- 「インフラ都合」がドメインを汚染する 🫠
じゃあ “設定” はどこで読むの? 🏠🔧
基本はこの3択です👇
- Web(入口):
Program.cs/ Controller など 🌐 - Infrastructure(外部):DB・外部API・メール送信など 🧱
- Application(ユースケース):ドメインに渡す値を組み立てる 🧑🍳
ドメインは 「値を受け取るだけ」 が理想です 🙆♀️✨
例1:外部APIの設定(これは “設定” でOK)🌍⚙️
外部APIの BaseUrl とか Timeout は、ビジネスルールじゃなくて 環境の都合 なので、appsettings.json に置いてOKです😊
appsettings.json 🗂️
{
"PaymentApi": {
"BaseUrl": "https://api.example.com",
"TimeoutSeconds": 10
}
}
Options クラス(Infrastructure か Web 側)🧩
public sealed class PaymentApiOptions
{
public required string BaseUrl { get; init; }
public int TimeoutSeconds { get; init; } = 10;
}
Program.cs でバインドして DI 登録 🧷
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddOptions<PaymentApiOptions>()
.Bind(builder.Configuration.GetSection("PaymentApi"))
.Validate(opt => !string.IsNullOrWhiteSpace(opt.BaseUrl), "PaymentApi:BaseUrl is required")
.Validate(opt => opt.TimeoutSeconds is >= 1 and <= 60, "TimeoutSeconds must be 1..60")
.ValidateOnStart();
var app = builder.Build();
app.Run();
✅これで「設定が壊れてたら起動時に落ちる」ので、事故が減ります🚑✨
例2:「税率」「送料」みたいな数値は注意⚠️(設定に見える罠🕳️)
たとえば TaxRate=0.1。
これ、つい appsettings.json に置きたくなるけど…👀
- 税率が “業務のルール” として扱われるなら 👉 ドメインの概念として持つ方が自然です 💡
- 環境で変わる(検証用・本番用で違う)だけなら 👉 設定でもOKです 🙆♀️
見分けのコツ(超大事)🧠✨
次の質問を自分にします👇
- それは「ビジネスの言葉」で説明する?(税率・手数料・締日…)📘
- それが変わると「仕様変更」扱い?(ルール変更)📝
- 監査・履歴・いつ変えたかが必要? 🕵️♀️
✅YESが多いほど「ドメイン寄り」です。
ドメインに “設定を渡す” 正しいやり方 🎁📦
ドメインはこういう感じが気持ちいいです👇 設定を読まずに、必要な値だけもらう✨
ドメイン側(設定の存在を知らない)🧠
public readonly record struct TaxRate(decimal Value)
{
public static TaxRate Create(decimal value)
=> value is >= 0m and <= 1m ? new(value) : throw new ArgumentOutOfRangeException(nameof(value));
}
public sealed class Order
{
public decimal Subtotal { get; }
public Order(decimal subtotal) => Subtotal = subtotal;
public decimal CalculateTotal(TaxRate taxRate)
=> Subtotal * (1m + taxRate.Value);
}
Application 側(設定→ドメイン型に変換して渡す)🍳
public sealed class BillingService
{
private readonly TaxRate _taxRate;
// ここで “設定” を受け取る(ドメインじゃないのでOK)
public BillingService(decimal taxRateFromConfig)
{
_taxRate = TaxRate.Create(taxRateFromConfig);
}
public decimal CalcTotal(decimal subtotal)
{
var order = new Order(subtotal);
return order.CalculateTotal(_taxRate);
}
}
💡ポイント: 「設定値(decimal)」をそのままドメインに入れないで、 ドメインの型(TaxRate)にしてから渡すのが強いです💪✨
“距離感” チェックリスト ✅📋✨
あなたのコードを見て、これが守れてたらかなり良いです😊
- Domain に
IConfiguration/IOptionsが出てこない 📵 - Domain は「設定キー名」を知らない(
"PaymentApi:BaseUrl"みたいなのが無い)🙅♀️ - 設定値は Application / Infrastructure で ドメイン型に変換してから渡してる 🔁
- 「ビジネスルールっぽい値」は、設定に置く前に一度疑ってる 🤔✨
AI(Copilot/Codex)に頼むときの “良いお願い” 🧑💻🤖💬
そのままコピペでOKです👇(地味に効きます✨)
appsettings.json の設定を IOptions<T> でバインドして。
ただし Domain プロジェクトには IConfiguration / IOptions を一切入れないで。
設定値は Application 層でドメイン型(Value Object)に変換してから渡す形にして。
ValidateOnStart も付けて、必須項目と範囲チェックも入れて。
ミニ演習(15分)🧪⏰✨
お題:外部API設定と、ビジネスルールっぽい値を分けよう😊
ExternalServices:WeatherApi(BaseUrl/Timeout)をIOptionsで登録 🌦️DiscountRateを「設定に置くべきか?」考える 🤔- ドメインに
DiscountRateの値オブジェクトを作って、Application から渡す 🎁
できたら最後に、自分のコードへ質問👇 「Domain が appsettings の存在を知ってない?」👀✨
次の第70章は、こうして分けた構造を 図にして一瞬で共有する回です 🗺️✨ (Mermaid / PlantUML で “迷わない地図” を作ります😊📌)