系列导航及源代码

需求

PUT请求本身其实可说的并不多,过程也和创建基本类似。在这篇文章中,重点是填上之前文章里留的一个坑,我们曾经给TodoItem定义过一个标记完成的领域事件:TodoItemCompletedEvent,在SaveChangesAsync方法里做了一个DispatchEvents的操作。并且在DomainEventService实现IDomainEventServicePublish方法中暂时以下面的代码代替了:

  • DomainEventService.cs
public async Task Publish(DomainEvent domainEvent)
{
// 在这里暂时什么都不做,到CQRS那一篇的时候再回来补充这里的逻辑
_logger.LogInformation("Publishing domain event. Event - {event}", domainEvent.GetType().Name);
}

在前几篇应用MediatR实现CQRS的过程中,我们主要是和IRequest/IRequestHandler打的交道。那么本文将会涉及到另外一对常用的接口:INotification/INotificationHandler,来实现领域事件的处理。

目标

  1. 实现PUT请求;
  2. 实现领域事件的响应处理;

原理与思路

实现PUT请求的原理和思路与实现POST请求类似,就不展开了。关于实现领域事件响应的部分,我们需要实现INotification/INotificationHandler接口,并改写Publish的实现,让它能发布领域事件通知。

实现

PUT请求

我们拿更新TodoItem的完成状态来举例,首先来自定义一个领域异常NotFoundException,位于Application/Common/Exceptions里:

  • NotFoundException.cs
namespace TodoList.Application.Common.Exceptions;

public class NotFoundException : Exception
{
public NotFoundException() : base() { }
public NotFoundException(string message) : base(message) { }
public NotFoundException(string message, Exception innerException) : base(message, innerException) { }
public NotFoundException(string name, object key) : base($"Entity \"{name}\" ({key}) was not found.") { }
}

创建对应的Command

  • UpdateTodoItemCommand.cs
using MediatR;
using TodoList.Application.Common.Exceptions;
using TodoList.Application.Common.Interfaces;
using TodoList.Domain.Entities; namespace TodoList.Application.TodoItems.Commands.UpdateTodoItem; public class UpdateTodoItemCommand : IRequest<TodoItem>
{
public Guid Id { get; set; }
public string? Title { get; set; }
public bool Done { get; set; }
} public class UpdateTodoItemCommandHandler : IRequestHandler<UpdateTodoItemCommand, TodoItem>
{
private readonly IRepository<TodoItem> _repository; public UpdateTodoItemCommandHandler(IRepository<TodoItem> repository)
{
_repository = repository;
} public async Task<TodoItem> Handle(UpdateTodoItemCommand request, CancellationToken cancellationToken)
{
var entity = await _repository.GetAsync(request.Id);
if (entity == null)
{
throw new NotFoundException(nameof(TodoItem), request.Id);
} entity.Title = request.Title ?? entity.Title;
entity.Done = request.Done; await _repository.UpdateAsync(entity, cancellationToken); return entity;
}
}

实现Controller:

  • TodoItemController.cs
[HttpPut("{id:Guid}")]
public async Task<ApiResponse<TodoItem>> Update(Guid id, [FromBody] UpdateTodoItemCommand command)
{
if (id != command.Id)
{
return ApiResponse<TodoItem>.Fail("Query id not match witch body");
} return ApiResponse<TodoItem>.Success(await _mediator.Send(command));
}

领域事件的发布和响应

首先需要在Application/Common/Models定义一个泛型类,实现INotification接口,用于发布领域事件:

  • DomainEventNotification.cs
using MediatR;
using TodoList.Domain.Base; namespace TodoList.Application.Common.Models; public class DomainEventNotification<TDomainEvent> : INotification where TDomainEvent : DomainEvent
{
public DomainEventNotification(TDomainEvent domainEvent)
{
DomainEvent = domainEvent;
} public TDomainEvent DomainEvent { get; }
}

接下来在Application/TodoItems/EventHandlers中创建对应的Handler

  • TodoItemCompletedEventHandler.cs
using MediatR;
using Microsoft.Extensions.Logging;
using TodoList.Application.Common.Models;
using TodoList.Domain.Events; namespace TodoList.Application.TodoItems.EventHandlers; public class TodoItemCompletedEventHandler : INotificationHandler<DomainEventNotification<TodoItemCompletedEvent>>
{
private readonly ILogger<TodoItemCompletedEventHandler> _logger; public TodoItemCompletedEventHandler(ILogger<TodoItemCompletedEventHandler> logger)
{
_logger = logger;
} public Task Handle(DomainEventNotification<TodoItemCompletedEvent> notification, CancellationToken cancellationToken)
{
var domainEvent = notification.DomainEvent; // 这里我们还是只做日志输出,实际使用中根据需要进行业务逻辑处理,但是在Handler中不建议继续Send其他Command或Notification
_logger.LogInformation("TodoList Domain Event: {DomainEvent}", domainEvent.GetType().Name); return Task.CompletedTask;
}
}

最后去修改我们之前创建的DomainEventService,注入IMediator并发布领域事件,这样就可以在Handler中进行响应了。

  • DomainEventService.cs
using MediatR;
using Microsoft.Extensions.Logging;
using TodoList.Application.Common.Interfaces;
using TodoList.Application.Common.Models;
using TodoList.Domain.Base; namespace TodoList.Infrastructure.Services; public class DomainEventService : IDomainEventService
{
private readonly IMediator _mediator;
private readonly ILogger<DomainEventService> _logger; public DomainEventService(IMediator mediator, ILogger<DomainEventService> logger)
{
_mediator = mediator;
_logger = logger;
} public async Task Publish(DomainEvent domainEvent)
{
_logger.LogInformation("Publishing domain event. Event - {event}", domainEvent.GetType().Name);
await _mediator.Publish(GetNotificationCorrespondingToDomainEvent(domainEvent));
} private INotification GetNotificationCorrespondingToDomainEvent(DomainEvent domainEvent)
{
return (INotification)Activator.CreateInstance(typeof(DomainEventNotification<>).MakeGenericType(domainEvent.GetType()), domainEvent)!;
}
}

验证

启动Api项目,更新TodoItem的完成状态。

  • 请求

  • 响应

  • 领域事件发布

总结

这篇文章主要在实现PUT请求的过程中介绍了如何通过MediatR去响应领域事件,我们用的示例代码中类似“创建TodoList”,包括后面会讲到的“删除TodoItem”之类的领域事件,都是相同的处理方式,我就不一一演示了。

可以看出来,在我们这个示例应用程序的框架基本搭建完毕以后,进行领域业务的开发的思路是比较清晰的,模块之间的耦合也处在一个理想的情况。

在我们来完成CRUD的最后一个请求之前,下一篇会简单地介绍一下PATCH请求的相关内容,这个请求实际应用比较少,但是为了保持知识树的完整性,还是会过一下。

最新文章

  1. Git权威指南 书摘
  2. mybatis批量删除提示类型错误
  3. Memcache所有方法及参数详解
  4. hbase0.95.2部署
  5. magento多语言中文语言包
  6. MongoDB的C#驱动程序教程(译) 转
  7. [nginx]Nginx禁止访问robots.txt防泄漏web目录
  8. iOS开发——UI篇Swift篇&amp;UIPickerView
  9. IO端口和IO内存的区别 转
  10. MyBatis入门教程(基于Mybatis3.2)
  11. html中的a标签的target属性的四个值的区别?
  12. 【Egret】3d 服务器配置
  13. Be the Best of Whatever You Are
  14. JaveScript内置对象(JS知识点归纳八)
  15. Git与远程reposiory的相关命令
  16. SQLMap入门之在Windows上安装SQLMap
  17. linux 学习笔记 显示压缩文件 gong.zip 的文件内容
  18. 将csv的数据导入mysql
  19. Libre 6003 「网络流 24 题」魔术球 (网络流,最大流)
  20. logstash匹配多行日志

热门文章

  1. 二进制免编译My SQL
  2. Equinox OSGi服务器应用程序的配置步骤 (支持JSP页面)
  3. mysql数据库备份脚本一例
  4. APICloud - 提交项目 点击右键 没有git这个选项
  5. SQLServer和java数据类型的对应关系
  6. 【C/C++】习题3-5 谜题/算法竞赛入门经典/数组和字符串
  7. 『学了就忘』Linux启动引导与修复 — 74、Linux系统的修复模式(光盘修复模式)
  8. linux系统目录初识
  9. [BUUCTF]PWN——ciscn_2019_es_2
  10. 转换…Transform…(Power Query 之 M 语言)