MediatR を使って .NET Core で CQRS を実装する

# 概要
MS docs に CQRS と DDD を適用した実装パターンに関する記事があります。
https://docs.microsoft.com/ja-jp/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/apply-simplified-microservice-cqrs-ddd-patterns

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/353069/2aacb562-f19f-6d83-ee05-ced022cc4b5f.png)

そこでは MediatR というライブラリを用いて CQRS の実装を行っています。
今回は自己理解の意味も込めて、ドメインモデル と Web API の実装サンプルを作成してみます。   

尚、CQRSやDDDに関する詳しい解説は、専門の皆様にお任せします。
(※) 筆者も勉強中の身です。。。

# コードサンプル
サンプルコードを Github にアップしています。
https://github.com/tYoshiyuki/dotnet-core-mediatr-sample
(※ サンプルコードを .NET6版 に更新しました！)

# 参考
ドメインモデルのコードは、下記を参考にさせていただきました。
https://github.com/nrslib/BottomUpDDDTheLaterPart

# 環境
- Windows10
- Visual Studio 2022
- .NET 6
- MediatR

# 解説

サンプルコードのプロジェクト構成は下記の通りです。

|プロジェクト名|説明|
|:--|:--|
|DotNetCoreMediatrSample.Domain|ドメインモデル|
|DotNetCoreMediatrSample.Infrastructure.InMemory|インメモリリポジトリの実装|
|DotNetCoreMediatrSample.Infrastructure.InMemory.Test|インメモリリポジトリのテスト|
|DotNetCoreMediatrSample.Web|Web API|

今回は Userエンティティを取得するクエリ と Userエンティティを作成するコマンド を実装します。
IRequest を実装したクラスをそれぞれ作成します。

```c#:GetUserQuery.cs

public class GetUserQuery : IRequest<UserModel>
{
    public GetUserQuery(string id)
    {
        Id = id;
    }

    public string Id { get; }
}

```

```c#:CreateUserCommand.cs

public class CreateUserCommand : IRequest
{
    public CreateUserCommand(string userName, string firstName, string familyName)
    {
        UserName = userName;
        FirstName = firstName;
        FamilyName = familyName;
    }

    public string UserName { get; }
    public string FirstName { get; }
    public string FamilyName { get; }
}

```

MediatR では クエリ・コマンド のメッセージに対応した処理を行うための Handler を実装する必要があります。 
IRequestHandler を実装する形で作成します。
尚、今回実装サンプルには含まれてませんが IRequest の代わりに INotification を利用すると 1つのメッセージに対して 複数のHandler を登録することが出来ます。

```c#:GetUserHandler.cs

public class GetUserHandler : IRequestHandler<GetUserQuery, UserModel>
{
    private readonly IUserRepository _repository;

    public GetUserHandler(IUserRepository repository)
    {
        _repository = repository;
    }

    public Task<UserModel> Handle(GetUserQuery request, CancellationToken cancellationToken)
    {
        var user = _repository.Find(new UserId(request.Id));
        var model = user == null ? null : new UserModel(user);
        return Task.FromResult(model);
    }
}


```

上記の内容を ASP.NET Core の Web API に組み込んでみます。
MediatR の Handler を DIコンテナ へ登録します。専用の拡張メソッドがあるため、これを利用します。
また、インメモリのリポジトリも合わせて DIコンテナ へ登録します。
尚、実践的な CQRS では クエリ と コマンド で対象となるデータストアを分ける場合があるのですが、今回は同一のリポジトリとしています。

```c#:Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddMediatR(typeof(GetUserHandler).Assembly);
    services.AddMediatR(typeof(CreateUserHandler).Assembly);
    services.AddControllers();

    services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo {Title = "Web API", Version = "v1"}); });

    services.AddTransient<IUserRepository, InMemoryUserRepository>();
    services.AddTransient<IUserFactory, UserFactory>();
}

```

Web APIのコントローラにて IMediator をコンストラクタインジェクションします。
IMediator.Send を利用し クエリ や コマンド を発行します。

```c#:UsersController.cs

public UsersController(IMediator mediator)
{
    _mediator = mediator;
}

[HttpGet("{id}")]
public async Task<ActionResult<UserViewModel>> Get(string id)
{
    var user = await _mediator.Send(new GetUserQuery(id));
    if (user == null) return BadRequest("データが存在しません");

    return new UserViewModel
    {
        Id = user.Id,
        UserName = user.UserName,
        FirstName = user.Name.FirstName,
        FamilyName = user.Name.FamilyName
    };
}

```

動作確認は Swagger UI より実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/353069/09e5facd-cda3-23f5-e188-510f19fb9488.png)

MediatR を用いることで比較的容易に ドメインロジック と Web API の依存関係を疎結合にする事が出来ます。
ASP.NET Core を利用する際の選択肢の一つとして、検討する価値があるのでは無いでしょうか。