在之前的文章中,我介绍了 Brighter 与 MS SQL Server 的集成 和 Brighter V10 RC1。本指南重点介绍迁移到 Brighter V10,着重讲解 MS SQL Server 配置更改和破坏性更新。
要求
.NET 8 或更高版本
一个包含以下 NuGet 包的 .NET 项目
- Paramore.Brighter.MessagingGateway.MsSql:启用 MS SQL Server 集成。
- Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection:启用使用 Microsoft 依赖注入 (DI) 注册 Brighter。
- Paramore.Brighter.ServiceActivator.Extensions.Hosting:将 Brighter 作为后台服务托管
- Serilog.AspNetCore:用于结构化日志记录(可选但推荐)。
Brighter 回顾
在继续讨论 SQL Server 配置之前,让我们回顾一下 Brighter 的基础知识。
请求 (命令/事件)
使用 IRequest 定义消息:
public class Greeting() : Event(Guid.NewGuid())
{
public string Name { get; set; } = string.Empty;
}
- 命令 (Commands):单接收者的操作(例如 SendEmail)。
- 事件 (Events):广播通知(例如 OrderShipped)。
消息映射器 (可选)
在 Brighter 消息和您的应用程序对象之间进行转换。对于异步工作流,映射器现在需要实现 IAmAMessageMapperAsync 接口。
请求处理程序
处理传入的消息:
public class GreetingHandler(ILogger<GreetingHandler> logger) : RequestHandler<Greeting>
{
public override Greeting Handle(Greeting command)
{
logger.LogInformation("你好 {Name}", command.Name);
return base.Handle(command);
}
}
配置 Brighter 使用 SQL Server
1. 创建队列表
Brighter 不会自动创建队列表。您可以使用以下脚本创建:
IF NOT (EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'QueueData'))
BEGIN
CREATE TABLE [dbo].[QueueData](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[Topic] [nvarchar](255) NOT NULL,
[MessageType] [nvarchar](1024) NOT NULL,
[Payload] [nvarchar](max) NOT NULL,
CONSTRAINT [PK_QueueData] PRIMARY KEY CLUSTERED ([Id] ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
CREATE NONCLUSTERED INDEX [IX_Topic] ON [dbo].[QueueData]([Topic] ASC) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
END;
2. 连接设置
定义 SQL Server 连接详情:
var config = new RelationalDatabaseConfiguration("<连接字符串>", queueStoreTable: "QueueData");
3. SQL 订阅
订阅一个主题 (Topic):
.AddServiceActivator(opt =>
{
opt.Subscriptions =
[
new MsSqlSubscription<Greeting>(
subscriptionName: new SubscriptionName("greeting.subscription"), // 订阅名称
channelName: new ChannelName("greeting.topic"), // 通道/主题名称
makeChannels: OnMissingChannel.Create, // 通道缺失时创建
messagePumpType: MessagePumpType.Reactor, // 消息泵类型
timeOut: TimeSpan.FromSeconds(10) // 超时时间
),
];
opt.DefaultChannelFactory = new ChannelFactory(new MsSqlMessageConsumerFactory(config)); // 默认通道工厂
})
4. SQL 生产者配置
发布事件到主题:
.UseExternalBus(opt =>
{
opt.ProducerRegistry = new MsSqlProducerRegistryFactory(config, [
new Publication<Greeting>
{
Topic = new RoutingKey("greeting.topic"), // 路由键/主题
MakeChannels = OnMissingChannel.Create // 通道缺失时创建
}]).Create(); // 创建生产者注册表
})
Brighter V10 中的破坏性变更
Brighter V10 引入了对 SQL 集成的更新。以下是关键的破坏性变更:
消息映射器重构
默认 JSON 序列化
在 V9 中,消息映射器是强制性的。在 V10 中,除非需要自定义逻辑,否则内置了 JSON 序列化。您也可以更改 Brighter 的默认序列化方式。
订阅 (Subscription)
订阅方面有两个主要变更。
显式消息泵类型 (Explicit Message Pump Types)
第一个变更是:之前我们有一个名为 runAsync 或 isAsync 的布尔字段。为了更清晰,我们将其改为 messagePumpType,其类型为 MessagePumpType(Reactor, Proactor, Unknown)。
属性重命名 (Property Renaming)
在 AddServiceActivator 方法中,我们将 ChannelFactory 属性重命名为 DefaultChannelFactory。
发布 (Publication)
使用 `ExternalBusConfiguration` 来配置生产者和发件箱 (Outbox) 模式:
// V10
.UseExternalBus(opt => { ... })
// V9
.UseExternalBus(new RmqProducerRegistryFactory(...)) // 示例中使用的是 RMQ,但概念类似
结论
Brighter V10 简化了 SQL Server 集成,同时引入了破坏性变更以提高清晰度和灵活性。关键更新包括内置序列化、显式的消息泵类型以及更简化的配置 API。有关完整的实现细节,请参考 GitHub 示例仓库(链接地址保持不变)。
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Paramore.Brighter;
using Paramore.Brighter.MessagingGateway.MsSql;
using Paramore.Brighter.ServiceActivator.Extensions.DependencyInjection;
using Paramore.Brighter.ServiceActivator.Extensions.Hosting;
using Serilog;
using Paramore.Brighter.Extensions.DependencyInjection;
using Paramore.Brighter.MsSql;
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();
await using (SqlConnection connection = new("Server=127.0.0.1,11433;Database=master;User Id=sa;Password=Password123!;Application Name=BrighterTests;Connect Timeout=60;Encrypt=false;"))
{
await connection.OpenAsync();
await using var command = connection.CreateCommand();
command.CommandText =
"""
IF DB_ID('BrighterTests') IS NULL
BEGIN
CREATE DATABASE BrighterTests;
END;
""";
_ = await command.ExecuteNonQueryAsync();
}
await using (SqlConnection connection = new("Server=127.0.0.1,11433;Database=BrighterTests;User Id=sa;Password=Password123!;Application Name=BrighterTests;Connect Timeout=60;Encrypt=false;"))
{
await connection.OpenAsync();
await using var command = connection.CreateCommand();
command.CommandText =
"""
IF NOT (EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'dbo' AND TABLE_NAME = 'QueueData'))
BEGIN
CREATE TABLE [dbo].[QueueData](
[Id] [bigint] IDENTITY(1,1) NOT NULL,
[Topic] [nvarchar](255) NOT NULL,
[MessageType] [nvarchar](1024) NOT NULL,
[Payload] [nvarchar](max) NOT NULL,
CONSTRAINT [PK_QueueData] PRIMARY KEY CLUSTERED ([Id] ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
CREATE NONCLUSTERED INDEX [IX_Topic] ON [dbo].[QueueData]([Topic] ASC) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
END;
""";
_ = await command.ExecuteNonQueryAsync();
}
var host = new HostBuilder()
.UseSerilog()
.ConfigureServices(static (_, services) =>
{
var config = new RelationalDatabaseConfiguration ("Server=127.0.0.1,11433;Database=BrighterTests;User Id=sa;Password=Password123!;Application Name=BrighterTests;Connect Timeout=60;Encrypt=false", queueStoreTable: "QueueData");
services
.AddHostedService<ServiceActivatorHostedService>()
.AddServiceActivator(opt =>
{
opt.Subscriptions = [
new MsSqlSubscription<Greeting>(
subscriptionName: new SubscriptionName("greeting.subscription"),
channelName: new ChannelName("greeting.topic"),
makeChannels: OnMissingChannel.Create,
messagePumpType: MessagePumpType.Reactor,
timeOut: TimeSpan.FromSeconds(10)
)
];
opt.DefaultChannelFactory= new ChannelFactory(new MsSqlMessageConsumerFactory(config));
})
.UseExternalBus(opt =>
{
opt.ProducerRegistry = new MsSqlProducerRegistryFactory(config, [
new Publication<Greeting>
{
Topic = new RoutingKey("greeting.topic"),
MakeChannels = OnMissingChannel.Create
}
]).Create();
})
.AutoFromAssemblies();
})
.Build();
await host.StartAsync();
while (true)
{
await Task.Delay(TimeSpan.FromSeconds(2));
Console.Write("Say your name (or q to quit): ");
var name = Console.ReadLine();
if (string.IsNullOrEmpty(name))
{
continue;
}
if (name == "q")
{
break;
}
var process = host.Services.GetRequiredService<IAmACommandProcessor>();
process.Post(new Greeting { Name = name });
}
await host.StopAsync();
public class Greeting() : Event(Guid.NewGuid())
{
public string Name { get; set; } = string.Empty;
}
public class GreetingHandler(ILogger<GreetingHandler> logger) : RequestHandler<Greeting>
{
public override Greeting Handle(Greeting command)
{
logger.LogInformation("Hello {Name}", command.Name);
return base.Handle(command);
}
}
846

被折叠的 条评论
为什么被折叠?



