ASP.NET Core中实现单体程序的事件发布/订阅

简介: ASP.NET Core中实现单体程序的事件发布/订阅 背景# 事件发布/订阅是一种非常强大的模式,它可以帮助业务组件间实现完全解耦,不同的业务组件只依赖事件,只关注哪些事件是需要自己处理的,而不用关注谁来处理自己发布事件,事件追溯(Event Sourcing)也是基于事件发布/订阅的。

ASP.NET Core中实现单体程序的事件发布/订阅

背景#

事件发布/订阅是一种非常强大的模式,它可以帮助业务组件间实现完全解耦,不同的业务组件只依赖事件,只关注哪些事件是需要自己处理的,而不用关注谁来处理自己发布事件,事件追溯(Event Sourcing)也是基于事件发布/订阅的。在微服务架构中,事件发布/订阅有非常多的应用场景。今天我给大家分享一个基于ASP.NET Core的单体程序使用事件发布/订阅的例子,针对分布式项目的事件发布/订阅比较复杂,难点是事务处理,后续我会另写一篇博文来演示。

案例说明#

当前我们有一个基于ASP.NET Core的电子商务系统,在项目的初期,业务非常简单,只有一个购物车模块和一个订单模块,所有的代码都放在一个项目中。

整个项目使用了一个简单的三层架构。

这里当用户提交购物车的时候,程序会在ShoppingCartManager类的SubmitShoppingCart方法中执行3个操作

  • 修改当前购物车的状态为完成
  • 根据购物车中的物品创建一个新订单
  • 给用户发邮件

代码如下:


 
 
Copy
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需要负责的,所以我们需要它们从这个方法中移出去,使用的方法就是事件订阅/发布。

新的架构图#

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

  • 这里我们会创建一个购物车提交事件ShoppingCartSubmittedEvent
  • 当站点启动的时候,我们会在一个名为EventHandlerContainer的类中注册订阅ShoppingCartSubmittedEvent事件的2个处理类CreateOrderHandlerConfirmEmailSentHandler
  • SubmitShoppingCart方法中,我们会做2件事情:
    • 更改当前购物车的状态。
    • 发布ShoppingCartSubmittedEvent事件。
  • CreateOrderHandler事件处理器会调用OrderManager类中的创建订单方法。
  • ConfirmEmailSentHandler事件处理器会负责发送邮件。

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

添加事件基类#

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


 
 
Copy
public class EventBase { public EventBase() { OccuredOn = DateTime.Now; } protected DateTime OccuredOn { get; set; } }

定义购物车提交事件#

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


 
 
Copy
public class ShoppingCartSubmittedEvent : EventBase { public ShoppingCartSubmittedEvent() { Items = new List<ShoppingCartSubmittedItem>(); } public List<ShoppingCartSubmittedItem> Items { get; set; } } public class ShoppingCartSubmittedItem { public string ItemId { get; set; } public string Name { get; set; } public decimal Price { get; set; } }

定义事件处理器接口#

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


 
 
Copy
public interface IEventHandler<T> where T : EventBase { void Run(T obj); Task RunAsync(T obj); }

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

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

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

CreateOrderHandler.cs


 
 
Copy
public class CreateOrderHandler : IEventHandler<ShoppingCartSubmittedEvent> { 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


 
 
Copy
public class ConfirmEmailSentHandler : IEventHandler<ShoppingCartSubmittedEvent> { 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


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

OrderManager.cs


 
 
Copy
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)

 
 
Copy
public class EventHandlerContainer { private IServiceProvider _serviceProvider = null; private static Dictionary<string, List<Type>> _mappings = new Dictionary<string, List<Type>>(); public EventHandlerContainer(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public static void Subscribe<T, THandler>() where T : EventBase where THandler : IEventHandler<T> { var name = typeof(T).Name; if (!_mappings.ContainsKey(name)) { _mappings.Add(name, new List<Type> { }); } _mappings[name].Add(typeof(THandler)); } public static void Unsubscribe<T, THandler>() where T : EventBase where THandler : IEventHandler<T> { var name = typeof(T).Name; _mappings[name].Remove(typeof(THandler)); if (_mappings[name].Count == 0) { _mappings.Remove(name); } } public void Publish<T>(T o) where T : EventBase { var name = typeof(T).Name; if (_mappings.ContainsKey(name)) { foreach (var handler in _mappings[name]) { var service = (IEventHandler<T>)_serviceProvider.GetService(handler); service.Run(o); } } } public async Task PublishAsync<T>(T o) where T : EventBase { var name = typeof(T).Name; if (_mappings.ContainsKey(name)) { foreach (var handler in _mappings[name]) { var service = (IEventHandler<T>)_serviceProvider.GetService(handler); await service.RunAsync(o); } } } }

代码解释:

  • 这里我没有直接订阅事件处理器的实例,而且订阅了事件处理器的类型
  • 多个事件处理器可以订阅同一个事件
  • EventHandlerContainer的构造函数中,我们注入了一个IServiceProvider,我们可以使用它来获得对应事件处理器的实例。

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

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


 
 
Copy
public void ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddScoped<IOrderManager, OrderManager>(); services.AddScoped<IShoppingCartManager, ShoppingCartManager>(); services.AddScoped<IShoppingCartRepository, ShoppingCartRepository>(); services.AddScoped<IOrderRepository, OrderRepository>(); services.AddScoped<IUnitOfWork, UnitOfWork>(); services.AddScoped<CreateOrderHandler>(); services.AddScoped<ConfirmEmailSentHandler>(); services.AddScoped<EventHandlerContainer>(); EventHandlerContainer.Subscribe<ShoppingCartSubmittedEvent, CreateOrderHandler>(); EventHandlerContainer.Subscribe<ShoppingCartSubmittedEvent, ConfirmEmailSentHandler>(); }

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

修改ShoppingCartManager#

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


 
 
Copy
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

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

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

2个事件处理器都被正确触发了。

总结#

至此我们的代码重构完成。 最终的代码中,SubmitShoppingCart方法,仅负责修改购物车状态并发布一个购物车提交的事件。生成订单和发送邮件的功能代码都被移动到了独立的处理类中。

这样的方式的好处不仅仅是完成了代码的解耦,针对后续的扩展也非常有利,想想一下,如果在未来当前项目需求追加这样一个功能,当提交购物车的时候,除了要发送确认邮件,还要发送手机短信。这时候你根本不需要去修改ShoppingCartManager类,你只需要针对ShoppingCartSubmittedEvent在再添加一个新的事件处理器即可,这也满足的SOLID的开闭原则。

作者:Lamond Lu
原文地址:https://www.cnblogs.com/lwqlun/p/10468058.html

相关文章
|
1月前
|
开发框架 .NET 开发者
简化 ASP.NET Core 依赖注入(DI)注册-Scrutor
Scrutor 是一个简化 ASP.NET Core 应用程序中依赖注入(DI)注册过程的开源库,支持自动扫描和注册服务。通过简单的配置,开发者可以轻松地从指定程序集中筛选、注册服务,并设置其生命周期,同时支持服务装饰等高级功能。适用于大型项目,提高代码的可维护性和简洁性。仓库地址:&lt;https://github.com/khellang/Scrutor&gt;
51 5
|
25天前
|
开发框架 算法 中间件
ASP.NET Core 中的速率限制中间件
在ASP.NET Core中,速率限制中间件用于控制客户端请求速率,防止服务器过载并提高安全性。通过`AddRateLimiter`注册服务,并配置不同策略如固定窗口、滑动窗口、令牌桶和并发限制。这些策略可在全局、控制器或动作级别应用,支持自定义响应处理。使用中间件`UseRateLimiter`启用限流功能,并可通过属性禁用特定控制器或动作的限流。这有助于有效保护API免受滥用和过载。 欢迎关注我的公众号:Net分享 (239字符)
49 1
|
2月前
|
开发框架 .NET C#
在 ASP.NET Core 中创建 gRPC 客户端和服务器
本文介绍了如何使用 gRPC 框架搭建一个简单的“Hello World”示例。首先创建了一个名为 GrpcDemo 的解决方案,其中包含一个 gRPC 服务端项目 GrpcServer 和一个客户端项目 GrpcClient。服务端通过定义 `greeter.proto` 文件中的服务和消息类型,实现了一个简单的问候服务 `GreeterService`。客户端则通过 gRPC 客户端库连接到服务端并调用其 `SayHello` 方法,展示了 gRPC 在 C# 中的基本使用方法。
56 5
在 ASP.NET Core 中创建 gRPC 客户端和服务器
|
1月前
|
算法 Java 测试技术
Benchmark.NET:让 C# 测试程序性能变得既酷又简单
Benchmark.NET是一款专为 .NET 平台设计的性能基准测试框架,它可以帮助你测量代码的执行时间、内存使用情况等性能指标。它就像是你代码的 "健身教练",帮助你找到瓶颈,优化性能,让你的应用跑得更快、更稳!希望这个小教程能让你在追求高性能的路上越走越远,享受编程带来的无限乐趣!
124 13
|
1月前
|
开发框架 缓存 .NET
GraphQL 与 ASP.NET Core 集成:从入门到精通
本文详细介绍了如何在ASP.NET Core中集成GraphQL,包括安装必要的NuGet包、创建GraphQL Schema、配置GraphQL服务等步骤。同时,文章还探讨了常见问题及其解决方法,如处理复杂查询、错误处理、性能优化和实现认证授权等,旨在帮助开发者构建灵活且高效的API。
37 3
|
23天前
|
监控 前端开发 API
一款基于 .NET MVC 框架开发、功能全面的MES系统
一款基于 .NET MVC 框架开发、功能全面的MES系统
|
4月前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
56 7
|
4月前
|
存储 开发框架 前端开发
ASP.NET MVC 迅速集成 SignalR
ASP.NET MVC 迅速集成 SignalR
102 0
|
5月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
75 0
|
5月前
|
开发框架 前端开发 安全
ASP.NET MVC 如何使用 Form Authentication?
ASP.NET MVC 如何使用 Form Authentication?

热门文章

最新文章