如何在不违反开闭原则的前提下将Azure服务总线从Topic切换到Queue?
符合开闭原则的Azure ServiceBus Topic转Queue最佳实践
开闭原则核心是对扩展开放,对修改关闭——也就是说不能改动你现有的AzureServiceBusSender代码,而是通过扩展的方式支持Queue对接,下面是几个公认的标准实现方法:
1. 基于现有抽象扩展Queue专属实现
你已经定义了IServiceBusSender接口,直接新建一个AzureQueueSender类实现该接口即可,完全不碰原有的Topic发送代码。
示例代码:
public class AzureQueueSender : IServiceBusSender { private ServiceBusClient _client; private ServiceBusSender _sender; // 构造函数接收队列名称而非主题名称 public AzureQueueSender(string connectionString, string queueName) { _client = new ServiceBusClient(connectionString); _sender = _client.CreateSender(queueName); } public async Task DisposeAsync() { await _sender.DisposeAsync(); await _client.DisposeAsync(); } public async Task SendMessageAsync(CustomMessage message, string action) { string json = JsonConvert.SerializeObject(message); var queueMessage = new ServiceBusMessage(json) { ContentType = "application/json" }; queueMessage.Settings.Add("Action", action); await _sender.SendMessageAsync(queueMessage); } }
这种方式完全遵循开闭原则:原有Topic发送逻辑不受任何影响,新增Queue支持只需要加新类,业务代码依赖IServiceBusSender接口即可,无需关心底层是Topic还是Queue。
2. 配置驱动+依赖注入实现无缝切换
结合.NET的依赖注入容器,通过配置文件来决定注册哪个实现,不需要修改业务代码就能在Topic和Queue之间切换。
比如在appsettings.json里加配置:
{ "ServiceBusSettings": { "ConnectionString": "your-connection-string", "TargetName": "your-queue-or-topic-name", "Type": "Queue" // 可选值:Queue/Topic } }
然后在Program.cs/Startup.cs里根据配置注册:
var serviceBusSettings = configuration.GetSection("ServiceBusSettings").Get<ServiceBusSettings>(); if (serviceBusSettings.Type.Equals("Queue", StringComparison.OrdinalIgnoreCase)) { services.AddScoped<IServiceBusSender>(sp => new AzureQueueSender(serviceBusSettings.ConnectionString, serviceBusSettings.TargetName)); } else { services.AddScoped<IServiceBusSender>(sp => new AzureServiceBusSender(serviceBusSettings.ConnectionString, serviceBusSettings.TargetName)); }
业务代码只需要注入IServiceBusSender即可,切换时只需要修改配置文件,完全符合开闭原则。
3. 提取公共逻辑到抽象基类(优化重复代码)
如果Topic和Queue的发送逻辑有大量重复(比如序列化、资源释放),可以把公共代码抽离到抽象基类,让两个实现类继承基类,只实现差异部分,减少代码冗余。
示例基类:
public abstract class ServiceBusSenderBase : IServiceBusSender { protected ServiceBusClient _client; protected ServiceBusSender _sender; public abstract Task DisposeAsync(); public async Task SendMessageAsync(CustomMessage message, string action) { string json = JsonConvert.SerializeObject(message); var serviceBusMessage = new ServiceBusMessage(json) { ContentType = "application/json" }; serviceBusMessage.Settings.Add("Action", action); await _sender.SendMessageAsync(serviceBusMessage); } }
然后修改原Topic实现和新增Queue实现:
// 原Topic实现,只保留差异逻辑 public class AzureServiceBusSender : ServiceBusSenderBase { public AzureServiceBusSender(string connectionString, string topicName) { _client = new ServiceBusClient(connectionString); _sender = _client.CreateSender(topicName); } public override async Task DisposeAsync() { await _sender.DisposeAsync(); await _client.DisposeAsync(); } } // Queue实现 public class AzureQueueSender : ServiceBusSenderBase { public AzureQueueSender(string connectionString, string queueName) { _client = new ServiceBusClient(connectionString); _sender = _client.CreateSender(queueName); } public override async Task DisposeAsync() { await _sender.DisposeAsync(); await _client.DisposeAsync(); } }
这种方式既保持了开闭原则(原代码只做了继承调整,没有修改核心逻辑),又解决了代码重复问题,是更优雅的实现方式。
内容的提问来源于stack exchange,提问作者Andrеw
相关产品推荐
相关产品推荐

