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

更多分享,请大家关注我的个人公众号:

 

 

 

 

posted @ 2022-09-20 09:30  黄明辉  阅读(974)  评论(0编辑  收藏  举报