.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的加载过程.虽然网上有很多文章介绍这部分内容,很多文章也是很久以前就已经出现了,但阅读之后 ...
随机推荐
- 利用Python爬虫刷店铺微博等访问量最简单有效教程
一.安装必要插件 测试环境:Windows 10 + Python 3.7.0 (1)安装Selenium pip install selenium (2)安装Requests pip install ...
- cmake打印shell
cmake链接库失败时,可通过打印路径下对应的lib来定位问题 execute_process(COMMAND ls -lt ${CMAKE_CURRENT_SOURCE_DIR}/lib #执行sh ...
- JSONObject解析json数据
首先先看一下我们要解析的json数据是什么样子的: 代码: String url="http://113.57.190.228:8001/Web/Report/GetBigMSKReport ...
- 读取txt文件内容,并按一定长度分页显示
private List<string> SaveContentUpload(FileUpload file) { List<string> list_content = ne ...
- 小鸟初学Shell编程(三)脚本不同执行方式的影响
执行命令的方式 执行Shell脚本的方式通常有以下四种 方式一:bash ./test.sh 方式二:./test.sh 方式三:source ./test.sh 方式四:. ./test.sh 执行 ...
- 小鸟初学Shell编程(一)认识Shell
开篇介绍 Linux里非常的有用的一个功能,这个功能就叫Shell脚本. Shell脚本在我日常开发工作里也占了非常重要的角色,项目中一些简单的工作我们都可以使用Shell脚本来完成,比如定时删除日志 ...
- win10笔记本电脑连wifi显示“无internet,安全”解决办法
吹一波, 不出意外的话,这应该是网上最全最详细的解决办法......毕竟妹子的电脑遇到了问题,咱一定要给她解决啊. 问题描述:连上了WiFi,显示“无Internet,安全”.但实际上她的电脑是有网的 ...
- windows zlib库编译步骤
下载地址 http://www.zlib.net/ 动态库下载地址 如果自己实在不想编译的,可以直接下载 https://download.csdn.net/download/zhangxuechao ...
- 读《TCP/IP详解》:TCP
TCP(Transmission Control Protocol,传输控制协议),位于传输层,提供一种面向连接.可靠的字节流服务. 字节流服务(Byte Stream Service)是指,为了方便 ...
- 对于不返回任何键列信息的 SelectCommand,不支持 DeleteCommand 的动态 SQL 生成
VS新增操作数据库出现如下报错. 原因是数据库表未添加主键 MySQL: CREATE TABLE Customer (SID integer, Last_Name ), First_Name ), ...