使用ASP.NET Core怎么实现单体程序的事件发布
创始人
2024-11-27 15:42:54
0次
 public void SubmitShoppingCart(string shoppingCartId)
 {
 var shoppingCart = _unitOfWork.ShoppingCartRepository
 .GetShoppingCart(shoppingCartId);

 _unitOfWork.ShoppingCartRepository
 .SubmitShoppingCart(shoppingCartId);

 _unitOfWork.OrderRepository
  .CreatOrder(new CreateOrderDTO
  {
  Items = shoppingCart.Items
   .Select(p => new NewOrderItemDTO
    {
    ItemId = p.ItemId,
    Name = p.Name,
    Price = p.Price
   }).ToList()
  });

 //这里为了简化代码,我用命令行表示发送邮件的逻辑
 Console.WriteLine("Confirm Email Sent.");

 _unitOfWork.Save();
 }

根据SOLID设计原则中的单一责任原则,如果一个类承担的职责过多,就等于把这些职责耦合在一起了。这里生成订单和发送邮件都不应该是当前SubmitShoppingCart需要负责的,所以我们需要它们从这个方法中移出去,使用的方法就是事件订阅/发布。

新的架构图

以下是使用事件发布/订阅之后的系统架构图。

使用ASP.NET Core怎么实现单体程序的事件发布

  • 这里我们会创建一个购物车提交事件ShoppingCartSubmittedEvent。

  • 当站点启动的时候,我们会在一个名为EventHandlerContainer的类中注册订阅ShoppingCartSubmittedEvent事件的2个处理类CreateOrderHandler和ConfirmEmailSentHandler。

  • 在SubmitShoppingCart方法中,我们会做2件事情:
           更改当前购物车的状态。
           发布ShoppingCartSubmittedEvent事件。

  • CreateOrderHandler事件处理器会调用OrderManager类中的创建订单方法。

  • ConfirmEmailSentHandler事件处理器会负责发送邮件。

好的,下面让我们来一步一步实现以上描述的代码。

添加事件基类

这里我们首先定义一个事件基类,其中暂时只添加了一个属性OccuredOn,它表示了当前事件的触发时间。

 public class EventBase
 {
 public EventBase()
 {
  OccuredOn = DateTime.Now;
 }

 protected DateTime OccuredOn
 {
  get;
  set;
 }
 }

定义购物车提交事件

接下来我们就需要创建购物车提交事件类ShoppingCartSubmittedEvent, 它继承自EventBase, 并提供了一个购物项集合

 public class ShoppingCartSubmittedEvent : EventBase
 {
 public ShoppingCartSubmittedEvent()
 {
  Items = new List();
 }

 public List Items { get; set; }
 }

 public class ShoppingCartSubmittedItem
 {
 public string ItemId { get; set; }

 public string Name { get; set; }

 public decimal Price { get; set; }

 }

定义事件处理器接口

为了添加事件处理器,我们首先需要定义一个泛型接口类IEventHandler

 public interface IEventHandler where T : EventBase
 {
 void Run(T obj);

 Task RunAsync(T obj);
 }

这个泛型接口类的是泛型类型必须继承自EventBase类。接口提供了2个方法Run和RunAsync。 它们定义了该接口的实现类必须实现同一个处理逻辑的同步和异步方法。

为购物车提交事件编写事件处理器

有了事件处理器接口,接下来我们就可以开始为购物车提交事件添加事件处理器了。这里我们为了实现前面定义的逻辑,我们需要创建2个处理器CreateOrderHandler和ConfirmEmailSentHandler

CreateOrderHandler.cs

 public class CreateOrderHandler : IEventHandler
 {
 private IOrderManager _orderManager = null;


 public CreateOrderHandler(IOrderManager orderManager)
 {
  _orderManager = orderManager;
 }

 public void Run(ShoppingCartSubmittedEvent obj)
 {
  _orderManager.CreateNewOrder(new Models.DTOs.CreateOrderDTO
  {
  Items = obj.Items.Select(p => new Models.DTOs.NewOrderItemDTO
  {
   ItemId = p.ItemId,
   Name = p.Name,
   Price = p.Price
  }).ToList()
  });
 }

 public Task RunAsync(ShoppingCartSubmittedEvent obj)
 {
  return Task.Run(() =>
  {
  Run(obj);
  });
 }
 }

代码解释:

  • 在CreateOrderHandler的构造函数中,我们注入了IOrderManager接口对象,CreateNewOrder负责最终创建订单的工作

  • 这里为了简化代码,我直接使用了Task.Run,并在其中调用了同步方法实现

ConfirmEmailSentHandler.cs

 public class ConfirmEmailSentHandler : IEventHandler
 {
 public void Run(ShoppingCartSubmittedEvent obj)
 {
  Console.WriteLine("Confirm Email Sent.");
 }

 public Task RunAsync(ShoppingCartSubmittedEvent obj)
 {
  return Task.Run(() =>
  {
  Console.WriteLine("Confirm Email Sent.");
  });
 }
 }

代码解释:

  • 这个处理类非常简单,为了简化代码,我仅输出了一行文本来表示实际需要运行的代码。

为OrderManager类添加创建订单方法

IOrderManager.cs

 public interface IOrderManager
 {
  string CreateNewOrder(CreateOrderDTO dto);
 }

OrderManager.cs

 public class OrderManager : IOrderManager
 {
  private IOrderRepository _orderRepository;

  public OrderManager(IOrderRepository orderRepository)
  {
   _orderRepository = orderRepository;
  }

  public string CreateNewOrder(CreateOrderDTO dto)
  {
   var orderId = _orderRepository.CreatOrder(dto);

   Console.WriteLine($"One order created: {JsonConvert.SerializeObject(dto)}");

   return orderId;
  }
 }

创建EventHandlerContainer

下面我们来编写最核心的事件处理器容器。在这里我们的事件处理器容器完成了3个功能

  • 订阅事件(Subscribe Event)

  • 取消订阅事件(Unsubscribe Event)

  • 发布事件(Publish Event)

 public class EventHandlerContainer
 {
  private IServiceProvider _serviceProvider = null;
  private static Dictionary> _mappings = new Dictionary>();

  public EventHandlerContainer(IServiceProvider serviceProvider)
  {
   _serviceProvider = serviceProvider;
  }

  public static void Subscribe()
   where T : EventBase
   where THandler : IEventHandler
  {
   var name = typeof(T).Name;

   if (!_mappings.ContainsKey(name))
   {
    _mappings.Add(name, new List { });
   }

   _mappings[name].Add(typeof(THandler));
  }

  public static void Unsubscribe()
   where T : EventBase
   where THandler : IEventHandler
  {
   var name = typeof(T).Name;
   _mappings[name].Remove(typeof(THandler));

   if (_mappings[name].Count == 0)
   {
    _mappings.Remove(name);
   }
  }

  public void Publish(T o) where T : EventBase
  {
   var name = typeof(T).Name;

   if (_mappings.ContainsKey(name))
   {
    foreach (var handler in _mappings[name])
    {
     var service = (IEventHandler)_serviceProvider.GetService(handler);

     service.Run(o);
    }
   }
  }

  public async Task PublishAsync(T o) where T : EventBase
  {
   var name = typeof(T).Name;

   if (_mappings.ContainsKey(name))
   {
    foreach (var handler in _mappings[name])
    {
     var service = (IEventHandler)_serviceProvider.GetService(handler);

     await service.RunAsync(o);
    }
   }
  }
 }

代码解释:

  • 这里我没有直接订阅事件处理器的实例,而且订阅了事件处理器的类型

  • 多个事件处理器可以订阅同一个事件

  • EventHandlerContainer的构造函数中,我们注入了一个IServiceProvider,我们可以使用它来获得对应事件处理器的实例。

在程序启动时,注册事件订阅

现在我们来Startup.cs的ConfigureServices方法,这里我们需要进行服务注册,并完成事件订阅。

 public void ConfigureServices(IServiceCollection services)
 {
  services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

  services.AddScoped();
  services.AddScoped();
  services.AddScoped();
  services.AddScoped();
  services.AddScoped();
  services.AddScoped();
  services.AddScoped();
  services.AddScoped();


  EventHandlerContainer.Subscribe();
  EventHandlerContainer.Subscribe();
 }

注意:这里保证一个Api请求中的所有数据库操作在一个事务里,这里我们使用Scoped作用域。这样我们就可以在调用工作单元IUnitOfWork接口的Save代码中启用事务。

修改ShoppingCartManager

最后我们来修改ShoppingCartManager, 改用发布事件的方式来完成后续创建订单和发送邮件的功能。

 public void SubmitShoppingCart(string shoppingCartId)
 {
  var shoppingCart = _unitOfWork.ShoppingCartRepository
   .GetShoppingCart(shoppingCartId);

  _unitOfWork.ShoppingCartRepository
   .SubmitShoppingCart(shoppingCartId);


  _container.Publish(new ShoppingCartSubmittedEvent()
  {
   Items = shoppingCart
     .Items
     .Select(p => new ShoppingCartSubmittedItem
     {
      ItemId = p.ItemId,
      Name = p.Name,
      Price = p.Price
     })
     .ToList()
  });
  
  _unitOfWork.Save();
 }

这样ShoppingCartManager就只需要关注购物车状态的变更,而不需要关注发送确认邮件和创建订单了。

最终效果

现在让我们启动项目,

首先我们使用[POST] /api/shoppingCarts来添加一个新的购物车, 这个API会返回当前购物车的Id

 使用ASP.NET Core怎么实现单体程序的事件发布

然后我们使用[PUT] /api/shoppingCarts/ShoppingCart_636872897140555966来模拟提交购物车,程序返回操作成功

使用ASP.NET Core怎么实现单体程序的事件发布 

最后我们查看一下控制台的输出日志

使用ASP.NET Core怎么实现单体程序的事件发布 

相关内容

热门资讯

空调连续工作多久需要停机休息 空调连续工作的时间取决于多个因素,包括空调的型号、功率、使用环境和维护情况等。一般来说,空调可以连续...
空调哪个符号是除湿的 空调除湿功能通常用一个水滴形状的符号表示。不同品牌和型号的空调遥控器可能会有所差异,但一般来说,除湿...
格力空调多长时间加一次氟利昂 1、一般情况下,空调对氟利昂的使用量是非常小的,所以在空调使用了四年左右的时候再考虑加也没问题。2、...
空调用多长时间需要加氟一次 一般情况下是五年。当出现以下这几种情况时,空调才需要加氟。1、空调的室内机和室外机各有两个接口,当接...
格调生活·C标思享荟:卡萨帝在... 9月12日的郑州龙湖金融岛,夜风轻拂,灯光璀璨。“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑...
品牌价值破千亿、高端份额持续领... 9月12日,“C标时刻共传奇——卡萨帝思享荟暨20周年品牌盛典郑州站”在郑州龙湖金融岛举办。2026...
空调主板坏了修800还是换新的 空调主板坏了是修还是换新,需要考虑多个因素,以下是一些建议:1. 维修成本:如果维修费用较低,比如 ...
空调开多久需要休息多长时间 问题:空调开多久需要休息多长时间回答:1、在使用空调的过程中,一般空调开7~8个小时左右需要休息2~...
中央空调多长时间清洗一次 1、一般空调开机前清洗一次即可,空调开机中间时段再清洗一次,空调关机时清洗一次,这样比较合理。2、空...
中央空调氟利昂多久加一次 看空调制热和制冷程度,很多人的观点认为氟利昂必须一年放一次,其实这个观点是错误的,理解上面有点偏差,...