ABP-VNEXT 学习笔记(六)事件总线--本地事件总线
事件总线,是我们在处理分布式和微服务的时候经常需要用到的,用于分布式事务解决方案。
事件总线基本就2个逻辑,1个发布事件,1个是订阅事件。 abp的本地事件是非异步的!!!
abp也提供了事件总线的处理机制
下面跟着学习本地事件总线
abp官网文档地址:https://docs.abp.io/zh-Hans/abp/latest/Local-Event-Bus
本地事件总线允许服务发布和订阅进程内事件. 这意味着如果两个服务(发布者和订阅者)在同一个进程中运行,那么它是合适的。这点很重要,也就是
本地事件,适用于发布者和订阅者都在同一个项目同一个进程里执行。他不适用于不同进程之间的协同。
事件可用于我们发生一个动作时,触发多种处理的操作,以往,我们都是在发生动作的业务逻辑里面写触发事件的调用函数。如果多个地方要触发,就需要多处写,不利于代码的解耦。
用了事件,就完全解耦并且是异步。
下面我们上代码,利用abp文档上的代码示例做训练讲解
ILocalEventBus
可以注入 ILocalEventBus
并且使用发布本地事件.
我们新建一个.NET6.0 的Asp.net Core MVC项目EventBus,nuget上引用Volo.Abp.Core、Volo.Abp.Autofac、Volo.Abp.AspNetCore.Mvc 包
然后一样创建一个EventBusModule.cs启动类,代码如下:
using Volo.Abp; using Volo.Abp.AspNetCore.Mvc; using Volo.Abp.Autofac; using Volo.Abp.Modularity; namespace EventBus { [DependsOn( typeof(AbpAutofacModule), typeof(AbpAspNetCoreMvcModule) )] public class EventBusModule:AbpModule { public override void ConfigureServices(ServiceConfigurationContext context) { } public override void OnApplicationInitialization(ApplicationInitializationContext context) { var app = context.GetApplicationBuilder(); var env = context.GetEnvironment(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); endpoints.MapControllerRoute( name: "areas", pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}" ); }); } } }
Program.cs文件如下:
using EventBus; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllersWithViews(); builder.Host.AddAppSettingsSecretsJson().UseAutofac(); //引入autofac作为IOC await builder.AddApplicationAsync<EventBusModule>(); //将abp模块入口初始化 var app = builder.Build(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); await app.InitializeApplicationAsync(); //初始化abp模块应用 app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); app.Run();
以上是项目的基本配置。
下面开始事件总线相关代码
示例: 产品的存货数量发生变化时发布本地事件
新建一个MyService.cs类,作为我们的服务调用类,类中定义一个ChangeStockCountAsync方法,在方法中发布一个事件
using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Local; namespace EventBus.Models { public class MyService : ITransientDependency { private readonly ILocalEventBus _localEventBus; public MyService(ILocalEventBus localEventBus) { _localEventBus = localEventBus; } public virtual async Task ChangeStockCountAsync(Guid productId, int newCount) { //TODO: IMPLEMENT YOUR LOGIC... //PUBLISH THE EVENT await _localEventBus.PublishAsync( new StockCountChangedEvent { ProductId = productId, NewCount = newCount } ); } } public class StockCountChangedEvent { public Guid ProductId { get; set; } public int NewCount { get; set; } } }
PublishAsync
方法需要一个参数:事件对象,它负责保持与事件相关的数据,是一个简单的普通类:
StockCountChangedEvent 事件对象,用于参数传递。
接下来,我们定义两个订阅类,来订阅处理这个事件
一个服务可以实现 ILocalEventHandler<TEvent>
来处理事件.
示例: 处理上面定义的StockCountChangedEvent
新建一个MyHandler.cs类
using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus; namespace EventBus.Models { public class MyHandler : ILocalEventHandler<StockCountChangedEvent>, ITransientDependency { public async Task HandleEventAsync(StockCountChangedEvent eventData) { //TODO: your code that does somthing on the event //这里处理事件 var proId = eventData.ProductId; Console.WriteLine("MyHandler执行完毕"); } } }
我们再定义一个MyHandler2.cs 的订阅类,用于验证一个事件,是可以多个订阅处理的
using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus; namespace EventBus.Models { public class MyHandler2 : ILocalEventHandler<StockCountChangedEvent>, ITransientDependency { public async Task HandleEventAsync(StockCountChangedEvent eventData) { //TODO: your code that does somthing on the event //这里处理事件 var proId = eventData.ProductId; Console.WriteLine("MyHandler2执行完毕"); } } }
OK,以上我们就完成了1个发布,2个订阅。
那我们跑起来看下,我们在Home控制器的index中调用Myservice中的ChangeStockCountAsync方法,验证2个订阅是否都执行了。
public MyService myService { get; set; } public HomeController(ILogger<HomeController> logger) { _logger = logger; } public IActionResult Index() { myService.ChangeStockCountAsync(Guid.NewGuid(), 100).GetAwaiter(); Console.WriteLine("任务执行完毕"); return View(); }
运行起来看结果:
通过运行结果,我们可以确认以下2点
1: 2个订阅都有执行,验证了1个发布,可以多个订阅的处理方式
2:订阅和发布是异步的,发布不需要等订阅执行完业务逻辑才结束。
3:很重点!!!虽然订阅是异步执行,但是主进程是需要等待订阅处理完才能返回,也就是一个请求,需要等待订阅处理完,才会返回响应内容。 若要使订阅不影响主进程执行,需要在订阅端自行用多线程处理。
下面,我们再进一步验证一个问题
一个订阅的事件处理方法,能否订阅多个事件。比如有多个动作,会触发相同的行为。比如入库要增加库存,退货也要增加库存。
那么入库定义为一个事件,退货也定义为一个事件,但是订阅事件是同一个,就是增加库存。
我们对代码进行优化一下来验证。
我们在 myservice.cs中增加事件对象:InStockCountChangedEvent
同时,为了简便,我们直接在原有的ChangeStockCountAsync方法中,增加InStockCountChangedEvent事件的发布
完整代码如下:
using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Local; namespace EventBus.Models { public class MyService : ITransientDependency { private readonly ILocalEventBus _localEventBus; public MyService(ILocalEventBus localEventBus) { _localEventBus = localEventBus; } public virtual async Task ChangeStockCountAsync(Guid productId, int newCount) { //TODO: IMPLEMENT YOUR LOGIC... //PUBLISH THE EVENT await _localEventBus.PublishAsync( new StockCountChangedEvent { ProductId = productId, NewCount = newCount } ); await _localEventBus.PublishAsync( new InStockCountChangedEvent { ProductId = productId, NewCount = newCount } ); } } public class StockCountChangedEvent { public Guid ProductId { get; set; } public int NewCount { get; set; } } public class InStockCountChangedEvent { public Guid ProductId { get; set; } public int NewCount { get; set; } } }
在MyHandler订阅事件处理中,我们增加InStockCountChangedEvent事件的订阅
代码如下:
using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus; namespace EventBus.Models { public class MyHandler : ILocalEventHandler<StockCountChangedEvent>, ILocalEventHandler<InStockCountChangedEvent>, ITransientDependency { public async Task HandleEventAsync(StockCountChangedEvent eventData) { //TODO: your code that does somthing on the event //这里处理事件 var proId = eventData.ProductId; Console.WriteLine("MyHandler处理StockCountChangedEvent事件完毕"); } public async Task HandleEventAsync(InStockCountChangedEvent eventData) { //TODO: your code that does somthing on the event //这里处理事件 var proId = eventData.ProductId; Console.WriteLine("MyHandler处理InStockCountChangedEvent事件完毕"); } } }
OK了,我们跑起来
根据运行结果,我们可以看到2个事件都有执行。
这也验证了,一个订阅事件处理类,可以同时订阅多个事件并处理。
有1个注意点是:它并不保证原子性,就是订阅端如果处理失败,那就没了。没有重试机制,也没有回滚。
源码:https://gitee.com/fei686868/EventBus
更多分享,请大家关注我的个人公众号: