概要
MS docs に CQRS と DDD を適用した実装パターンに関する記事があります。
https://docs.microsoft.com/ja-jp/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/apply-simplified-microservice-cqrs-ddd-patterns
そこでは 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 を実装したクラスをそれぞれ作成します。
public class GetUserQuery : IRequest<UserModel>
{
public GetUserQuery(string id)
{
Id = id;
}
public string Id { get; }
}
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 を登録することが出来ます。
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 では クエリ と コマンド で対象となるデータストアを分ける場合があるのですが、今回は同一のリポジトリとしています。
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 を利用し クエリ や コマンド を発行します。
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
};
}
MediatR を用いることで比較的容易に ドメインロジック と Web API の依存関係を疎結合にする事が出来ます。
ASP.NET Core を利用する際の選択肢の一つとして、検討する価値があるのでは無いでしょうか。