领域事件可以触发应用内领域对象变化的通知,结合实时消息(例如SignalR)可以将通知推送给外部应用。但是消息推送的可靠性如何保证?ABP框架能集成消息队列吗?当然可以,下面就是示例。
1.实时数据传输与消息队列
实时数据传输和消息队列是两类不同的技术方案,有着不同的应用场景,但又有一定的相似性。实时数据传输更偏重于“实时”两个字,要求保证数据的及时有效交换,多用于多媒体相关的业务场景,常见的技术栈有WebRTC、SignalR等;消息队列更偏重于“可靠”,多用于能够容忍一定延迟但要求数据可靠到达的业务场景,常见的技术栈有ActiveMQ、RabbitMQ、Kafka等。相似的地方是,实时数据传输与消息队列基本都有诸如分布式、高并发之类的解决方案,而且常用的也都是订阅/发布模式。订阅/发布模式也并不是实时数据传输或消息队列的专利,很多开发语言、技术栈都有这样的机制,这样的机制也更有利于我们快速使用拥有订阅/发布模式的技术栈。
简单谈了下我对实时数据传输和消息队列的浅薄理解,回到本篇的主题–在ABP框架中集成消息队列。ABP官方似乎并没有提供单独的集成消息队列的开发包,Redis可以承担消息队列的功能,但是其他几种消息队列怎么办?倒是可以根据ABP框架的接口实现常见几种消息队列的模块。但是懒人就想问:有没有现成的?当然有,下面就安利下开源框架dotnetcore/CAP。
2.安装并配置CAP
暂不细究CAP是什么,先看能干什么。打开NuGet管理器为应用层AbpDemo.Application安装DotNetCore.CAP程序包,为分布式服务层和展现层AbpDemo.Web安装DotNetCore.CAP.RabbitMQ和DotNetCore.CAP.MySql程序包。
之后在Web项目的Startup启动类中添加AddCAP方法。
public class Startup
{
private readonly IConfigurationRoot _appConfiguration;
public IServiceProvider ConfigureServices(IServiceCollection services)
{
//...
#region CAP
services.AddCap(x =>
{
//配置数据库连接
string connectionString = _appConfiguration["ConnectionStrings:Default"];
x.UseMySql(connectionString);
//配置消息队列RabbitMQ
x.UseRabbitMQ(option =>
{
option.HostName = _appConfiguration["MqSettings:MqHost"];
option.VirtualHost = _appConfiguration["MqSettings:MqVirtualHost"];
option.UserName = _appConfiguration["MqSettings:MqUserName"];
option.Password = _appConfiguration["MqSettings:MqPassword"];
});
});
#endregion
//...
}
//...
}
然后在Application项目的应用服务中添加带有CAP特性的方法。包括订阅数据的方法和发布数据的方法。
- 订阅数据
/// <summary>
/// 数据订阅服务
/// </summary>
public class SubscribeAppService : ISubscribeAppService
{
/// <summary>
/// 货品数据同步
/// </summary>
/// <param name="goods"></param>
[CapSubscribe("goods-sync")]
public void SubscribeGoods(Goods goods)
{
//...
}
}
public interface ISubscribeAppService:ICapSubscribe
{
void SubscribeGoods(Goods goods);
}
另外不要忘了在Web项目的Startup启动类中注册数据订阅服务。
services.AddTransient<ISubscribeAppService, SubscribeAppService>();
- 发布数据
/// <summary>
/// 货品管理-应用服务
/// </summary>
public class GoodsAppService: AbpDemoAppServiceBase<Goods,DetailGoodsDto,string,CreateGoodsDto,UpdateGoodsDto,PagedGoodsDto>,IGoodsAppService
{
private readonly IGoodsRecordManager _goodsRecordManager;//出入库记录领域服务
private readonly IGoodsManager _goodsManager;//货品管理领域服务
private readonly IMessageManager _messageManager;//实时消息领域服务
private readonly ICapPublisher _capPublisher;//数据发布器
public GoodsAppService(IRepository<Goods,string> repository,
IGoodsRecordManager goodsRecordManager,
IGoodsManager goodsManager,
IMessageManager messageManager,
ICapPublisher capPublisher) :base(repository)
{
_goodsRecordManager = goodsRecordManager;
_goodsManager = goodsManager;
_messageManager = messageManager;
EventBus = NullEventBus.Instance;
_capPublisher = capPublisher;
}
public override Task<DetailGoodsDto> Create(CreateGoodsDto input)
{
//新增货品时同步至备份库
Goods goods = input.MapTo<Goods>();
_capPublisher.Publish<Goods>("goods-sync", goods);//发布数据
return base.Create(input);
}
}
可能写到这里还没说清楚上面一堆代码要干什么。简单来说,现在很多的分布式应用都存在分库或分应用的场景。还是以之前的货品管理为例,假设货品管理的应用程序连接数据库且需要数据同步,也就是主应用的数据变化时需要将数据同步到其他应用。上面一系列的代码描述的就是这一过程:应用连接MySQL数据库,通过API接口新增货品时,将新增货品的数据发布到应用所连接的消息队列RabbitMQ中,对应的是goods-sync这个主题,订阅了对应主题的应用接收到推送的新增货品数据后继续进行后续操作。
3.数据同步记录和仪表盘展示
项目启动后,CAP会自动在数据库添加两张表cap.published和cap.received,分别用于记录发布和订阅数据的历史记录。
另外如果喜欢可视化的UI界面,可以安装DotNetCore.CAP.Dashboard程序包添加一个可视化的仪表盘界面。
在上一步的AddCAP代码段中添加如下代码:
services.AddCap(x =>
{
x.UseDashboard();//仪表盘
//...
});
启动项目后,打开地址http://xxxx:xx/cap就能访问UI界面了。
4.关于DotNetCore.CAP
通过上面的例子粗略说明了CAP能干什么,再来说说CAP是什么。先看一张官方给出的示意图。
根据官方的描述,CAP是一个分布式事务解决方案,能够在分布式系统中(SOA,MicroService)实现事件总线及最终一致性,基于 .NET Core 技术,具有轻量级、易用性、高性能等特点。我个人是把CAP理解为一座桥梁,一座能够连接数据库、消息队列、应用的桥梁和中转站。
目前DotNetCore.CAP在数据库方面支持SQL Server、MySQL、PostgreSQL、MongoDB等,在消息队列方面支持Kafka、RabbitMQ、AzureService等,实用性很强,再次安利。