LoginSignup
15
14

More than 1 year has passed since last update.

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

Last updated at Posted at 2019-10-08

概要

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

そこでは 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 を実装したクラスをそれぞれ作成します。

GetUserQuery.cs

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

    public string Id { get; }
}

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 を登録することが出来ます。

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 では クエリ と コマンド で対象となるデータストアを分ける場合があるのですが、今回は同一のリポジトリとしています。

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 を利用し クエリ や コマンド を発行します。

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

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

15
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
14