迁移至 Brighter V10 并集成 Microsoft SQL Server

在之前的文章中,我介绍了 Brighter 与 MS SQL Server 的集成 和 Brighter V10 RC1。本指南重点介绍迁移到 Brighter V10,着重讲解 MS SQL Server 配置更改和破坏性更新。

要求

.NET 8 或更高版本
一个包含以下 NuGet 包的 .NET 项目

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,其类型为 MessagePumpTypeReactor, 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);
    }
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值