.Net Core 基于CAP框架的事件总线
.Net Core 基于CAP框架的事件总线
CAP 是一个在分布式系统中(SOA,MicroService)实现事件总线及最终一致性(分布式事务)的一个开源的 C# 库,她具有轻量级,高性能,易使用等特点。
github:https://github.com/dotnetcore/CAP
CAP是一款优秀的框架,但是CAP在消息订阅的处理类必须使用[CapSubscribe]特性来绑定,本人觉得不是很科学.
好在2.5.1版本开放了接口IConsumerServiceSelector为public,提供了扩展的基础,以下将基于CAP,扩展成简洁的事件总线组件.
使用:
/// <summary>
/// 修改用户名事件
/// </summary>
public class UserNameUpdateEvent : IEvent
{
public int Id { get; set; } public string OldName { get; set; } public string NewName { get; set; }
} /// <summary>
/// 发布事件,你可以在任意地方注入IEventPublisher,进行事件发布
/// </summary>
public class PublishEvent : Guc.Kernel.Dependency.ITransient
{
public PublishEvent(IEventPublisher eventPublisher)
{
EventPublisher = eventPublisher;
} IEventPublisher EventPublisher { get; } public void Publish()
{
EventPublisher.Publish(new UserNameUpdateEvent
{
Id = ,
OldName = "老王1",
NewName = "老王2"
});
}
} /// <summary>
/// 事件处理,你可以注入任何需要的类型
/// </summary>
public class UserNameUpdateEventHandler : IEventHandler<UserNameUpdateEvent>
{
public UserNameUpdateEventHandler(IUserStore userStore, ILogger<UserNameUpdateEventHandler> logger)
{
UserStore = userStore;
Logger = logger;
} IUserStore UserStore { get; }
ILogger<UserNameUpdateEventHandler> Logger { get; } /// <summary>
/// 执行事件处理
/// </summary>
/// <param name="event">事件对象</param>
/// <returns></returns>
public async Task Execute(UserNameUpdateEvent @event)
{
Logger.LogInformation($"修改用户名:{@event.OldName}->{@event.NewName}");
await Task.CompletedTask;
UserStore.Update(new UserModel { Id = @event.Id, Name = @event.NewName });
}
}
约定事件类型的FullName为事件名称,如例中的:UserNameUpdateEvent的类型FullName:Guc.Sample.UserNameUpdateEvent;
约定事件处理类型的FullName为GroupName,如例中的:UserNameUpdateEventHandler 的类型FullName:Guc.Sample.UserNameUpdateEventHandler
如果使用的RabbitMQ作为消息的传输,则Guc.Sample.UserNameUpdateEvent为路由的Key,Guc.Sample.UserNameUpdateEventHandler 为队列名称.
使用:
services.AddGucKernel()
.AddEventBus(capOptions =>
{
// CAP相关的配置 });
扩展代码,github:https://github.com/280780363/guc/blob/master/src/Guc.EventBus/GucConsumerServiceSelector.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.Extensions.DependencyInjection;
using System.Collections.Concurrent;
using DotNetCore.CAP;
using Guc.Kernel.Utils;
using Microsoft.Extensions.Options; namespace Guc.EventBus
{
class GucConsumerServiceSelector : IConsumerServiceSelector
{
private readonly CapOptions _capOptions;
private readonly IServiceProvider _serviceProvider; /// <summary>
/// since this class be designed as a Singleton service,the following two list must be thread safe!!!
/// </summary>
private readonly ConcurrentDictionary<string, List<RegexExecuteDescriptor<ConsumerExecutorDescriptor>>> _asteriskList;
private readonly ConcurrentDictionary<string, List<RegexExecuteDescriptor<ConsumerExecutorDescriptor>>> _poundList; /// <summary>
/// Creates a new <see cref="DefaultConsumerServiceSelector" />.
/// </summary>
public GucConsumerServiceSelector(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
_capOptions = serviceProvider.GetService<IOptions<CapOptions>>().Value; _asteriskList = new ConcurrentDictionary<string, List<RegexExecuteDescriptor<ConsumerExecutorDescriptor>>>();
_poundList = new ConcurrentDictionary<string, List<RegexExecuteDescriptor<ConsumerExecutorDescriptor>>>();
} public IReadOnlyList<ConsumerExecutorDescriptor> SelectCandidates()
{
var executorDescriptorList = new List<ConsumerExecutorDescriptor>(); executorDescriptorList.AddRange(FindConsumersFromInterfaceTypes(_serviceProvider));
return executorDescriptorList;
} public ConsumerExecutorDescriptor SelectBestCandidate(string key, IReadOnlyList<ConsumerExecutorDescriptor> executeDescriptor)
{
var result = MatchUsingName(key, executeDescriptor);
if (result != null)
{
return result;
} //[*] match with regex, i.e. foo.*.abc
result = MatchAsteriskUsingRegex(key, executeDescriptor);
if (result != null)
{
return result;
} //[#] match regex, i.e. foo.#
result = MatchPoundUsingRegex(key, executeDescriptor);
return result;
} private IEnumerable<ConsumerExecutorDescriptor> FindConsumersFromInterfaceTypes(
IServiceProvider provider)
{
var executorDescriptorList = new List<ConsumerExecutorDescriptor>(); using (var scoped = provider.CreateScope())
{
var scopedProvider = scoped.ServiceProvider;
var consumerServices = scopedProvider.GetServices<ICapSubscribe>();
foreach (var service in consumerServices)
{
var typeInfo = service.GetType().GetTypeInfo(); // 必须是非抽象类
if (!typeInfo.IsClass || typeInfo.IsAbstract)
continue; // 继承自IEventHandler<>
if (!typeInfo.IsChildTypeOfGenericType(typeof(IEventHandler<>)))
continue; executorDescriptorList.AddRange(GetTopicAttributesDescription(typeInfo));
} return executorDescriptorList;
}
} private List<string> GetEventNamesFromTypeInfo(TypeInfo typeInfo)
{
List<string> names = new List<string>();
foreach (var item in typeInfo.ImplementedInterfaces)
{
var @interface = item.GetTypeInfo();
if (!@interface.IsGenericType)
continue;
if (@interface.GenericTypeArguments.Length != )
continue; var eventType = @interface.GenericTypeArguments[].GetTypeInfo();
if (!eventType.IsChildTypeOf<IEvent>())
continue; names.Add(eventType.FullName);
}
return names;
} private IEnumerable<ConsumerExecutorDescriptor> GetTopicAttributesDescription(TypeInfo typeInfo)
{
var names = GetEventNamesFromTypeInfo(typeInfo);
if (names.IsNullOrEmpty())
return new ConsumerExecutorDescriptor[] { }; List<ConsumerExecutorDescriptor> results = new List<ConsumerExecutorDescriptor>();
var methods = typeInfo.GetMethods();
foreach (var eventName in names)
{
var method = methods.FirstOrDefault(r => r.Name == "Execute"
&& r.GetParameters().Length ==
&& r.GetParameters()[].ParameterType.FullName == eventName
&& r.GetParameters()[].ParameterType.IsChildTypeOf<IEvent>());
if (method == null)
continue; results.Add(new ConsumerExecutorDescriptor
{
Attribute = new CapSubscribeAttribute(eventName) { Group = typeInfo.FullName + "." + _capOptions.Version },
ImplTypeInfo = typeInfo,
MethodInfo = method
});
} return results;
} private ConsumerExecutorDescriptor MatchUsingName(string key, IReadOnlyList<ConsumerExecutorDescriptor> executeDescriptor)
{
return executeDescriptor.FirstOrDefault(x => x.Attribute.Name == key);
} private ConsumerExecutorDescriptor MatchAsteriskUsingRegex(string key, IReadOnlyList<ConsumerExecutorDescriptor> executeDescriptor)
{
var group = executeDescriptor.First().Attribute.Group;
if (!_asteriskList.TryGetValue(group, out var tmpList))
{
tmpList = executeDescriptor.Where(x => x.Attribute.Name.IndexOf('*') >= )
.Select(x => new RegexExecuteDescriptor<ConsumerExecutorDescriptor>
{
Name = ("^" + x.Attribute.Name + "$").Replace("*", "[0-9_a-zA-Z]+").Replace(".", "\\."),
Descriptor = x
}).ToList();
_asteriskList.TryAdd(group, tmpList);
} foreach (var red in tmpList)
{
if (Regex.IsMatch(key, red.Name, RegexOptions.Singleline))
{
return red.Descriptor;
}
} return null;
} private ConsumerExecutorDescriptor MatchPoundUsingRegex(string key, IReadOnlyList<ConsumerExecutorDescriptor> executeDescriptor)
{
var group = executeDescriptor.First().Attribute.Group;
if (!_poundList.TryGetValue(group, out var tmpList))
{
tmpList = executeDescriptor
.Where(x => x.Attribute.Name.IndexOf('#') >= )
.Select(x => new RegexExecuteDescriptor<ConsumerExecutorDescriptor>
{
Name = ("^" + x.Attribute.Name + "$").Replace("#", "[0-9_a-zA-Z\\.]+"),
Descriptor = x
}).ToList();
_poundList.TryAdd(group, tmpList);
} foreach (var red in tmpList)
{
if (Regex.IsMatch(key, red.Name, RegexOptions.Singleline))
{
return red.Descriptor;
}
} return null;
} private class RegexExecuteDescriptor<T>
{
public string Name { get; set; } public T Descriptor { get; set; }
}
}
}
.Net Core 基于CAP框架的事件总线的更多相关文章
- 【DDD-Apwork框架】事件总线和事件聚合器
第一步:事件总线和事件聚合器 [1]事件总线 IEventBus IUnitOfWork.cs using System; using System.Collections.Generic; usin ...
- Asp.net Core基于MVC框架实现PostgreSQL操作
简单介绍 Asp.net Core最大的价值在于跨平台.跨平台.跨平台.重要的事情说三遍.但是目前毕竟是在开发初期,虽然推出了1.0.0 正式版,但是其实好多功能还没有完善.比方说编译时的一些文件编码 ...
- [Abp vNext 源码分析] - 13. 本地事件总线与分布式事件总线 (Rabbit MQ)
一.简要介绍 ABP vNext 封装了两种事件总线结构,第一种是 ABP vNext 自己实现的本地事件总线,这种事件总线无法跨项目发布和订阅.第二种则是分布式事件总线,ABP vNext 自己封装 ...
- 【Shashlik.EventBus】.NET 事件总线,分布式事务最终一致性
[Shashlik.EventBus].NET 事件总线,分布式事务最终一致性 简介 github https://github.com/dotnet-shashlik/shashlik.eventb ...
- C#事件总线
目录 简介 实现事件总线 定义事件基类 定义事件参数基类 定义EventBus 使用事件总线 事件及事件参数 定义发布者 定义订阅者 实际使用 总结 参考资料 简介 事件总线是对发布-订阅模式的一种实 ...
- .NET Core 事件总线,分布式事务解决方案:CAP 基于Kafka
背景 相信前面几篇关于微服务的文章也介绍了那么多了,在构建微服务的过程中确实需要这么一个东西,即便不是在构建微服务,那么在构建分布式应用的过程中也会遇到分布式事务的问题,那么 CAP 就是在这样的背景 ...
- .NET Core 事件总线,分布式事务解决方案:CAP
背景 相信前面几篇关于微服务的文章也介绍了那么多了,在构建微服务的过程中确实需要这么一个东西,即便不是在构建微服务,那么在构建分布式应用的过程中也会遇到分布式事务的问题,那么 CAP 就是在这样的背景 ...
- ASP.NET Core Web API下事件驱动型架构的实现(三):基于RabbitMQ的事件总线
在上文中,我们讨论了事件处理器中对象生命周期的问题,在进入新的讨论之前,首先让我们总结一下,我们已经实现了哪些内容.下面的类图描述了我们已经实现的组件及其之间的关系,貌似系统已经变得越来越复杂了. 其 ...
- 重温.NET下Assembly的加载过程 ASP.NET Core Web API下事件驱动型架构的实现(三):基于RabbitMQ的事件总线
重温.NET下Assembly的加载过程 最近在工作中牵涉到了.NET下的一个古老的问题:Assembly的加载过程.虽然网上有很多文章介绍这部分内容,很多文章也是很久以前就已经出现了,但阅读之后 ...
随机推荐
- 我的周记12——"站在现在看前人对未来的预测,很有意思"
万里风云三尺剑,一庭花草半床书 聊聊最近很火的5G B站有个up主 老师好我叫何同学 优秀啊,点赞. 他做了一个5G测速的视频火啦 视频链接:https://www.bilibili.com/vi ...
- Practical Go: Real world advice for writing maintainable Go programs
转自:https://dave.cheney.net/practical-go/presentations/qcon-china.html?from=timeline 1. Guiding pri ...
- kali渗透综合靶机(六)--FristiLeaks靶机
kali渗透综合靶机(六)--FristiLeaks靶机 靶机地址下载:https://download.vulnhub.com/fristileaks/FristiLeaks_1.3.ova 一.主 ...
- VMware+windbg时快照功能的使用
VMware+windbg时快照功能的使用 一.快照功能的使用 我们在编写与调试驱动文件时经常会面临死机蓝屏情况,如果像平常一样重启,再重新配置环境,肯定会占用大量时间. Vmware虚拟机给我们提供 ...
- 【USTC】雨
自九月初来到科大,到现在已经一个月.这几天是国庆假期(祖国七十华诞,祝福祖国),我没有回家,白天在实验室,晚上去找小许. 今天下雨了,不大,但是温度降了大约10度,上次下雨还是九月初开学那几日. 9月 ...
- C# 跨线程访问UI不报错,必须使用Invoke。
代码有时跨线程访问UI,修改按钮Enable属性不报异常.调试发现修改按钮属性的线程是Background,执行不报异常. 在窗体构造中添加 Control.CheckForIllegalCrossT ...
- [笔记] NuGet Warning NU5100 处理
问题描述 在将 .NET 项目编译成 NUGET 包时,如果项目引用了其它 dll 文件,则可能出现这个警告,如果不处理,Nuget 包中可能无法正确引用所添加的 dll . 处理方式 在项目 .cs ...
- centos下非yum方式安装docker环境
1.下载docker源码包 docker官网地址: https://download.docker.com/linux/static/stable/ 选择.tgz的包下载,例如:https://dow ...
- js中this关键字的作用
this中的几种情况 1.普通函数中的this window 2.构造函数中的this 是当前构造函数创建的对象在new这个构造函数的时候会在内存中创建一个对象,此时会让this指向刚创建好的这个对象 ...
- Linux环境oracle导库步骤
1.xshell登录linux 2.切换oracle用户 su - oracle 3.创建directory仓库目录,存放数据库dmp文件 //DIRFILE_zy 表示目录名称 后面的是实际地址 c ...