Skip to main content

第83章:Mediator ②:デファクト(MediatR)を使う🤝✨

ねらい 🎯

MediatRによるパイプライン処理のイメージ

  • Mediator(仲介役) を、.NET界隈の定番ライブラリ MediatR で体験するよ😊
  • 「呼び出し側が何を知らなくてよくなるの?」を、DI + ハンドラ + パイプラインで“目で見て”理解する✨
  • ついでに、「Request(1個のハンドラ)」と「Notification(複数ハンドラ)」の違いもスッキリさせる🧠🌟

到達目標 ✅

  • AddMediatR でハンドラを登録して、IMediator.Send(...) で呼び出せる 🤝
  • 呼び出し側がハンドラの具象クラスを一切知らない状態を作れる 🙈✨
  • IPipelineBehavior を1つ入れて、横断関心(ログなど)を“外側”に逃がせる 🌈
  • 「MediatRの“便利さ”と“やりすぎ危険”」の境界を言葉にできる ⚖️

手順 🧩🛠️

1) MediatRの“いま”を知る(2026-02-06時点)🗞️✨

  • MediatRは v14.0.0 が公開されていて、.NET 10対応のリリースが出てるよ 📦🌟 (NuGet)
  • また、最近の流れとして 商用(Commercial)まわりの扱いがあるので、導入時に「ライセンスキー設定が必要になるケース」を必ず確認してね🔑⚠️(NuGetページに SetLicenseKey(...) の例が載ってるよ) (NuGet)
  • 参考:.NET自体は .NET 10 がLTS、2026-02-06時点で 10.0.2(2026-01-13) が最新パッチとして案内されてるよ🧡 (Microsoft)

2) NuGetで MediatR を追加する 📦✨

Visual Studioでプロジェクトを右クリック → NuGet パッケージの管理MediatR を追加!

PowerShellでやるならこんな感じ(※プロジェクトのフォルダで)👇

dotnet add package MediatR

ちなみに昔は MediatR.Extensions.Microsoft.DependencyInjection が別リポジトリだったけど、今は移動済み(古いほうはアーカイブ)だよ〜🗂️ (GitHub)

3) “最小のRequest/Handler”を作る(まずは Send だけ)🚀

ここでは「注文確定っぽい操作」を Request(=1個のハンドラ) でやるよ💡 ポイントは、呼び出し側が PlaceOrderHandler を一切知らないこと😊

using MediatR;

public sealed record PlaceOrderCommand(Guid OrderId, decimal Total)
: IRequest<PlaceOrderResult>;

public sealed record PlaceOrderResult(bool Success, string Message);

ハンドラ(処理本体)はこれ👇

using MediatR;
using Microsoft.Extensions.Logging;

public sealed class PlaceOrderHandler
: IRequestHandler<PlaceOrderCommand, PlaceOrderResult>
{
private readonly ILogger<PlaceOrderHandler> _logger;

public PlaceOrderHandler(ILogger<PlaceOrderHandler> logger)
=> _logger = logger;

public Task<PlaceOrderResult> Handle(PlaceOrderCommand request, CancellationToken cancellationToken)
{
_logger.LogInformation("OrderId={OrderId}, Total={Total}", request.OrderId, request.Total);

if (request.Total <= 0)
return Task.FromResult(new PlaceOrderResult(false, "合計金額が0以下はダメだよ💦"));

return Task.FromResult(new PlaceOrderResult(true, "注文確定OK〜🎉"));
}
}

4) DIに MediatR を登録して起動する(AddMediatR)🔌✨

AddMediatR はこんな感じで アセンブリからハンドラ登録してくれるよ〜便利!🥳 (公式の例として RegisterServicesFromAssemblyContaining<...>() が案内されてる) (NuGet)

using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var builder = Host.CreateApplicationBuilder(args);

builder.Services.AddLogging(x => x.AddConsole());

// ★ここがMediatR登録(このProgramがいるアセンブリ内のハンドラをスキャン)
builder.Services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssemblyContaining<Program>();

// (必要なら)ライセンスキー設定が必要なケースもあるよ⚠️
// cfg.SetLicenseKey(Environment.GetEnvironmentVariable("MEDIATR_LICENSE_KEY"));
});

using var host = builder.Build();

var mediator = host.Services.GetRequiredService<IMediator>();

var result = await mediator.Send(new PlaceOrderCommand(Guid.NewGuid(), 1200m));
Console.WriteLine($"{result.Success} / {result.Message}");

💡ここでの“勝ちポイント” 呼び出し側(Program)は Handlerを知らない → でも処理できる🎉 「依存の爆発」を “仲介役(Mediator)” に寄せてスッキリさせる感じだよ🕊️✨

5) “Notification(複数ハンドラ)”も軽く触る 📣✨

Requestは基本 1ハンドラ。 一方で Notificationは 複数ハンドラOK(「注文が確定した!」をいろんな人が購読するイメージ)💌✨

using MediatR;

public sealed record OrderPlaced(Guid OrderId, decimal Total) : INotification;
using MediatR;
using Microsoft.Extensions.Logging;

public sealed class SendEmailOnOrderPlaced : INotificationHandler<OrderPlaced>
{
private readonly ILogger<SendEmailOnOrderPlaced> _logger;
public SendEmailOnOrderPlaced(ILogger<SendEmailOnOrderPlaced> logger) => _logger = logger;

public Task Handle(OrderPlaced notification, CancellationToken cancellationToken)
{
_logger.LogInformation("メール通知したよ📧 OrderId={OrderId}", notification.OrderId);
return Task.CompletedTask;
}
}

public sealed class WriteAuditLogOnOrderPlaced : INotificationHandler<OrderPlaced>
{
private readonly ILogger<WriteAuditLogOnOrderPlaced> _logger;
public WriteAuditLogOnOrderPlaced(ILogger<WriteAuditLogOnOrderPlaced> logger) => _logger = logger;

public Task Handle(OrderPlaced notification, CancellationToken cancellationToken)
{
_logger.LogInformation("監査ログを書いたよ📝 OrderId={OrderId}", notification.OrderId);
return Task.CompletedTask;
}
}

呼び出し側はこう👇

await mediator.Publish(new OrderPlaced(Guid.NewGuid(), 1200m));

ここまでできると、第84章の「複数ハンドラへ」の気持ちよさが見えてくるよ〜😆✨

6) “横断関心”は IPipelineBehavior に逃がす(ログ/計測/検証など)🌈🧼

MediatRの強みの1つがこれ! IPipelineBehavior<TRequest, TResponse>Request(Send)に対して前後処理を挟める仕組みだよ(※Notificationじゃなくて Request 側だよ) (github-wiki-see.page)

まずはビヘイビアを1個作る👇

using MediatR;
using Microsoft.Extensions.Logging;

public sealed class LoggingBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
{
private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;

public LoggingBehavior(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
=> _logger = logger;

public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
_logger.LogInformation("➡️ Request: {RequestType}", typeof(TRequest).Name);

var response = await next();

_logger.LogInformation("✅ Response: {ResponseType}", typeof(TResponse).Name);
return response;
}
}

登録(AddOpenBehavior)👇

builder.Services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssemblyContaining<Program>();
cfg.AddOpenBehavior(typeof(LoggingBehavior<,>));
});

✅ ビヘイビアの実行順は「登録順と逆(最後に登録したものが外側)」って覚えると事故りにくいよ🧠✨ (github-wiki-see.page)

7) Visual Studioで“ここだけ読めばいい”ポイントを押さえる 🔍👀

迷子にならない読書ポイントはここ👇

  • IMediatorSend / Publish が見える(できることの全体像)
  • IRequestHandler<,>:Request → Response の契約
  • INotificationHandler<>:イベント購読(複数OK)
  • IPipelineBehavior<,>:横断関心を挟む“外側の輪”

「全部読まない」!ここだけでOKだよ🙆‍♀️✨


落とし穴 ⚠️😵

  1. Mediatorを“何でも屋の神様”にしちゃう 🙏💥
  • 「全部Sendで投げとけばいいじゃん!」ってなると、逆に追跡が難しくなるよ〜🥲
  • 目安:**アプリ層の入口(UseCase)**に寄せるのが安全✨
  1. ハンドラが太る(ビジネスロジック全部詰め込み) 🍔💦
  • ハンドラは“交通整理”くらいにして、実処理は専用クラスへ分けると綺麗✨
  1. Request と Notification の使い分けがグチャる 🍝😵
  • Request:基本1ハンドラ(結果が欲しい)
  • Notification:複数ハンドラ(お知らせしたい) ここを混ぜると設計の会話が崩れるよ⚠️
  1. パイプラインに詰めすぎる 🎒💦
  • ログ/計測/簡単な検証くらいは最高だけど、 “業務の本体”をパイプラインに入れだすと読めなくなるよ〜😇
  1. ライセンス/導入条件を見落とす 🔑⚠️
  • SetLicenseKey(...) が必要な構成もあるので、導入時にNuGetページの案内を必ずチェックしてね! (NuGet)

演習 🏃‍♀️💨

演習A:Request(Send)を1本通す🎯

  • PlaceOrderCommand を作る
  • PlaceOrderHandler で「合計が0以下なら失敗」を返す
  • mediator.Send(...) で結果表示 🎉

演習B:Notification(Publish)で“2人に通知”📣📣

  • OrderPlaced : INotification を作る
  • INotificationHandler<OrderPlaced>2つ作る(例:メール担当/監査担当)
  • Publish(...) で両方動くのをログで確認✨

演習C:パイプラインでログを挟む🌈

  • LoggingBehavior<,> を追加
  • Sendの前後にログが出るのを確認(「外側の輪」感を味わう)🔁✨

チェック ✅🧡

  • 呼び出し側が Handler名を一切書かずに処理できてる?🙈
  • Request(Send)と Notification(Publish)の違いを説明できる?🗣️
  • IPipelineBehavior が「横断関心の置き場所」って理解できた?🌈
  • 便利さに酔って「何でもMediator」になりそうな兆候、言語化できる?⚠️
  • 導入時に バージョン/ライセンス設定の確認ポイントを押さえた?🔑 (NuGet)