zoukankan      html  css  js  c++  java
  • 重新整理 .net core 实践篇—————领域事件[二十九]

    前文

    前面整理了仓储层,工作单元模式,同时简单介绍了一下mediator。

    那么就mediator在看下领域事件启到了什么作用吧。

    正文

    这里先注册一下MediatR服务:

    // 注册中间者:MediatR 
    services.AddMediatRServices();
    

    具体注册:

    /// <summary>
    /// 注册 ???
    /// </summary>
    /// <param name="services"></param>
    /// <returns></returns>
    public static IServiceCollection AddMediatRServices(this IServiceCollection services)
    {
    	// 注册事务流程管理类
    	services.AddTransient(typeof(IPipelineBehavior<,>), typeof(DomainContextTransactionBehavior<,>));
    
    	// package: MediatR.Extensions.Microsoft.Dependency
    	return services.AddMediatR(typeof(Order).Assembly, typeof(Program).Assembly);
    }
    

    前文提及在共享层的领域抽象类库中,有下面几个类:

    用来标志领域事件的接口:

    /// <summary>
    /// 领域事件接口
    /// 用来标记我们某一个对象是否是领域事件
    /// </summary>
    public interface IDomainEvent : INotification
    {
    }
    

    用来标志领域处理的接口:

    /// <summary>
    /// 领域事件处理器接口
    /// </summary>
    public interface IDomainEventHandler<TDomainEvent> : INotificationHandler<TDomainEvent> where TDomainEvent : IDomainEvent
    {
    	//这里我们使用了INotificationHandler的Handle方法来作为处理方法的定义,所以无需重新定义
    	//Task Handle(TDomainEvent domainEvent, CancellationToken cancellationToken);
    }
    

    实际上就是在mediator的INotification和INotificationHandler 封装一层。

    那么是否有必要封装呢?其实我们大多数使用别人的库,如果不是静态调用最好封装一层,这样可以让上层看来依赖于下层的IDomainEvent和IDomainEventHandler,而不是和某个框架耦合在一起。

    比如说有一个框架是Mediator的升级版,兼容了Mediator的功能,但是暴露出来的接口是INotificationPlusHandler,如果上层去耦合的话,改动的地方就有点多,风险也就越高,这不符合稳定性。

    下面是实体抽象类:

    /// <summary>
    /// 实体抽象类(包含多个主键的实体接口)
    /// </summary>
    public abstract class Entity : IEntity
    {
    	public abstract object[] GetKeys();
    
    	public override string ToString()
    	{
    		return $"[Entity:{GetType().Name}] Keys = {string.Join(",", GetKeys())}";
    	}
    
    	#region 领域事件定义处理 DomainEvents
    
    	/// <summary>
    	/// 领域事件集合
    	/// </summary>
    	private List<IDomainEvent> _domainEvents;
    
    	/// <summary>
    	/// 获取当前实体对象领域事件集合(只读)
    	/// </summary>
    	public IReadOnlyCollection<IDomainEvent> DomainEvents => _domainEvents?.AsReadOnly();
    
    	/// <summary>
    	/// 添加领域事件至当前实体对象领域事件结合中
    	/// </summary>
    	/// <param name="eventItem"></param>
    	public void AddDomainEvent(IDomainEvent eventItem)
    	{
    		_domainEvents = _domainEvents ?? new List<IDomainEvent>();
    		_domainEvents.Add(eventItem);
    	}
    
    	/// <summary>
    	/// 移除指定领域事件
    	/// </summary>
    	/// <param name="eventItem"></param>
    	public void RemoveDomainEvent(IDomainEvent eventItem)
    	{
    		_domainEvents?.Remove(eventItem);
    	}
    
    	/// <summary>
    	/// 清空所有领域事件
    	/// </summary>
    	public void ClearDomainEvents()
    	{
    		_domainEvents?.Clear();
    	}
    
    	#endregion
    }
    
    /// <summary>
    /// 实体抽象类(包含唯一主键Id的实体接口)
    /// </summary>
    /// <typeparam name="TKey">主键ID类型</typeparam>
    public abstract class Entity<TKey> : Entity, IEntity<TKey>
    {
    	int? _requestedHasCode;
    	public virtual TKey Id { get; protected set; }
    	public override object[] GetKeys()
    	{
    		return new object[] { Id };
    	}
    
    	/// <summary>
    	/// 对象是否想等
    	/// </summary>
    	/// <param name="obj"></param>
    	/// <returns></returns>
    	public override bool Equals(object obj)
    	{
    		if (obj == null || !(obj is Entity<TKey>))
    		{
    			return false;
    		}
    
    		if (Object.ReferenceEquals(this, obj))
    		{
    			return true;
    		}
    
    		Entity<TKey> item = (Entity<TKey>)obj;
    
    		if (item.IsTransient() || this.IsTransient())
    		{
    			return false;
    		}
    		else
    		{
    			return item.Id.Equals(this.Id);
    		}
    	}
    
    	public override int GetHashCode()
    	{
    		if (!IsTransient())
    		{
    			if (!_requestedHasCode.HasValue)
    			{
    				_requestedHasCode = this.Id.GetHashCode() ^ 31; // TODO
    			}
    			return _requestedHasCode.Value;
    		}
    		else
    		{
    			return base.GetHashCode();
    		}
    	}
    
    	/// <summary>
    	/// 对象是否为全新创建的,未持久化的
    	/// </summary>
    	/// <returns></returns>
    	public bool IsTransient()
    	{
    		return EqualityComparer<TKey>.Default.Equals(Id, default);
    	}
    	public override string ToString()
    	{
    		return $"[Entity:{GetType().Name}] Id = {Id}";
    	}
    
    	/// <summary>
    	/// == 操作符重载
    	/// </summary>
    	/// <param name="left"></param>
    	/// <param name="right"></param>
    	/// <returns></returns>
    	public static bool operator ==(Entity<TKey> left,Entity<TKey> right)
    	{
    		if (Object.Equals(left,null))
    		{
    			return (Object.Equals(right, null)) ? true : false;
    		}
    		else
    		{
    			return left.Equals(right);
    		}
    	}
    
    	/// <summary>
    	/// != 操作符重载
    	/// </summary>
    	/// <param name="left"></param>
    	/// <param name="right"></param>
    	/// <returns></returns>
    	public static bool operator !=(Entity<TKey> left, Entity<TKey> right)
    	{
    		return !(left == right);
    	}
    }
    

    那么我们在领域层中,我们的实体可以这样定义:

    /// <summary>
    /// 订单实体
    /// </summary>
    public class Order : Entity<long>, IAggregateRoot
    {
    	// 实体内字段的 set 方法都是 private 的
    	// 实体类型相关的数据操作,都应该是由我们实体来负责,而不是被外部的对象去操作
    	// 这样的好处是让我们的领域模型符合封闭开放的原则
    
    	public string UserId { get; private set; }
    	public string UserName { get; private set; }
    	public Address Address { get; private set; }
    	public int ItemCount { get; set; }
    
    	protected Order()
    	{
    	}
    
    	public Order(string userId, string userName, int itemCount, Address address)
    	{
    		this.UserId = userId;
    		this.UserName = userName;
    		this.ItemCount = itemCount;
    		this.Address = address;
    
    		// 构造新的Order对象的时候,添加一个创建Order领域事件
    		this.AddDomainEvent(new OrderCreatedDomainEvent(this));
    	}
    
    	/// <summary>
    	/// 修改收货地址
    	/// </summary>
    	/// <param name="address"></param>
    	public void ChangeAddress(Address address)
    	{
    		this.Address = address;
    
    		// 同样的,在修改地址操作时,也该定义一个类似的修改地址领域事件
    		//this.AddDomainEvent(new OrderAddressChangedDomainEvent(this));
    	}
    }
    

    在实例化的时候将创建一个OrderCreatedDomainEvent事件,这个后面是用来处理订单创建完成之后的事件。

    那么现在看下这个OrderCreatedDomainEvent是什么:

    /// <summary>
    /// 创建Order领域事件
    /// </summary>
    public class OrderCreatedDomainEvent : IDomainEvent
    {
    	/// <summary>
    	/// 写入私有,读取公开
    	/// </summary>
    	public Order Order { get; private set; }
    	public OrderCreatedDomainEvent(Order order)
    	{
    		this.Order = order;
    	}
    }
    

    那么在我们的应用层可以定义一个订单创建完成的事件处理类,比如说OrderCreatedDomainEventHandler:

    /// <summary>
    /// 创建Order领域事件处理
    /// </summary>
    public class OrderCreatedDomainEventHandler : IDomainEventHandler<OrderCreatedDomainEvent>
    {
    	ICapPublisher _capPublisher;
    	public OrderCreatedDomainEventHandler(ICapPublisher capPublisher)
    	{
    		_capPublisher = capPublisher;
    	}
    
    	/// <summary>
    	/// 领域事件处理
    	/// </summary>
    	/// <param name="notification"></param>
    	/// <param name="cancellationToken"></param>
    	/// <returns></returns>
    	public async Task Handle(OrderCreatedDomainEvent notification, CancellationToken cancellationToken)
    	{
    		// 当创建新订单时,向 EventBus 发布一个事件
    		await _capPublisher.PublishAsync("OrderCreated", new OrderCreatedIntegrationEvent(notification.Order.Id));
    		//_capPublisher.Publish("OrderCreated", new OrderCreatedIntegrationEvent(notification.Order.Id));
    		//return Task.CompletedTask;
    	}
    }
    

    上面表示,当我们创建一个订单后完成后的类,如果订单创建完毕,会向 EventBus 发布一个事件。

    上面是订单处理完成后的事件,那么我们创建订单是不是也应该写一个请求呢?

    为什么我们写请求,而不是写事件呢?一个是因为请求与请求处理是1对1,事件与事件处理是一对多。第二个是跟符合意境,一切的源头写出请求更好,还有就是创建订单的确在生活中像一个请求。

    CreateOrderCommand 如下:

    /// <summary>
    /// 创建订单 Command
    /// </summary>
    public class CreateOrderCommand : IRequest<long>
    {
    	public long ItemCount { get; private set; }
    
    	// public CreateOrderCommand() { }
    	public CreateOrderCommand(int itemCount)
    	{
    		ItemCount = itemCount;
    	}
    }
    

    具体的订单创建事件CreateOrderCommandHandler:

    /// <summary>
    /// 领域事件:订单创建命令处理程序
    /// 注:在创建完我们的领域模型并将其保存之后才会触发该处理程序
    /// </summary>
    public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, long>
    {
    	IOrderRepository _orderRepository;
    	ICapPublisher _capPublisher;
    
    	public CreateOrderCommandHandler(IOrderRepository orderRepository, ICapPublisher capPublisher)
    	{
    		_orderRepository = orderRepository;
    		_capPublisher = capPublisher;
    	}
    
    	/// <summary>
    	/// 处理订单创建命令
    	/// </summary>
    	/// <param name="request"></param>
    	/// <param name="cancellationToken"></param>
    	/// <returns></returns>
    	public async Task<long> Handle(CreateOrderCommand request, CancellationToken cancellationToken)
    	{
    		var address = new Address("wen san lu", "hangzhou", "310000");
    		var order = new Order("xiaohong1999", "小红", (int)request.ItemCount, address);
    
    		_orderRepository.Add(order);
    		await _orderRepository.UnitOfWork.SaveEntitiesAsync(cancellationToken);
    		return order.Id;
    	}
    }
    

    那么这个就会去处理相应的创建订单。

    那么测试一下:

    /// <summary>
    /// 创建订单
    /// </summary>
    /// <param name="cmd"></param>
    /// <returns></returns>
    [HttpPost]
    public async Task<long> CreateOrder([FromBody] CreateOrderCommand cmd)
    {
    	// 中间者,发送订单创建命令
    	return await _mediator.Send(cmd, HttpContext.RequestAborted);
    }
    

    第一步调用这里:

    第二步调用CreateOrderCommandHandler,去调用相应的订单调用事件:

    第三步,调用订单创建完的中间处理,也就是订单创建完的事件分发:

    代码如下:

    /// <summary>
    /// 中间者,领域事件发布扩展类
    /// </summary>
    public static class MediatorExtension
    {
    	/// <summary>
    	/// 领域事件发布,执行事件发送
    	/// </summary>
    	/// <param name="mediator"></param>
    	/// <param name="ctx"></param>
    	/// <returns></returns>
    	public static async Task DispatchDomainEventsAsync(this IMediator mediator, DbContext ctx)
    	{
    		// 1. 从当前要保存的 EntityContext 里面去跟踪实体
    		//    从跟踪到的实体对象中,获取到我们当前的 Event
    		var domainEntities = ctx.ChangeTracker
    								.Entries<Entity>()
    								.Where(x => x.Entity.DomainEvents != null && x.Entity.DomainEvents.Any());
    
    		// Events 类型转换
    		var domainEvents = domainEntities.SelectMany(x => x.Entity.DomainEvents).ToList();
    
    		// 2. 将实体内的 Events 清除 
    		domainEntities.ToList().ForEach(entity => entity.Entity.ClearDomainEvents());
    
    		// 3. 将所有的 Event 通过中间者发送出去
    		//    发出后,并找到对应的 handle 进行处理
    		foreach (var domainEvent in domainEvents)
    		{
    			await mediator.Publish(domainEvent);
    		}
    	}
    }
    

    第四步 调用创建订单之后的回调事件,也就是第三步中的分发:

    这里可能疑问DispatchDomainEventsAsync是怎么触发的?

    这里是在EFContext中的save中:

    /// <summary>
    /// 保存实体变更
    /// </summary>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public async Task<bool> SaveEntitiesAsync(CancellationToken cancellationToken = default)
    {
    	var result = await base.SaveChangesAsync(cancellationToken);
    
    	// 执行发送领域事件 
    	await _mediator.DispatchDomainEventsAsync(this);
    
    	return true;
    }
    

    然后调用这个就会去找到相应事件,然后调用publish,找到事件处理类进行handle方法调用:

    /// <summary>
    /// 领域事件发布,执行事件发送
    /// </summary>
    /// <param name="mediator"></param>
    /// <param name="ctx"></param>
    /// <returns></returns>
    public static async Task DispatchDomainEventsAsync(this IMediator mediator, DbContext ctx)
    {
    	// 1. 从当前要保存的 EntityContext 里面去跟踪实体
    	//    从跟踪到的实体对象中,获取到我们当前的 Event
    	var domainEntities = ctx.ChangeTracker.Entries<Entity>().Where(x => x.Entity.DomainEvents != null && x.Entity.DomainEvents.Any());
    
    	// Events 类型转换
    	var domainEvents = domainEntities.SelectMany(x => x.Entity.DomainEvents).ToList();
    
    	// 2. 将实体内的 Events 清除 
    	domainEntities.ToList().ForEach(entity => entity.Entity.ClearDomainEvents());
    
    	// 3. 将所有的 Event 通过中间者发送出去
    	//    发出后,并找到对应的 handle 进行处理
    	foreach (var domainEvent in domainEvents)
    	{
    		await mediator.Publish(domainEvent);
    	}
    }
    

    下一节Mediator的介绍。

  • 相关阅读:
    《css世界》学习摘要
    微信小程序知识点积累
    事件冒泡 事件委派
    遍历后台返回数据
    初识open stack
    keystone初识
    KVM详解
    openstack详解
    NoSQL之Redis集群理论
    gfs分布式文件系统
  • 原文地址:https://www.cnblogs.com/aoximin/p/14906540.html
Copyright © 2011-2022 走看看