結合DI,實現發布者與訂閱者的解耦,屬於本次事務的對象主體不應定義為訂閱者,因為訂閱者不應與發布者產生任何關聯
一、發布者訂閱者模式
發布者發出一個事件主題,一個或多個訂閱者接收這個事件,中間通過事件總線通訊(消息隊列),並且發布者與訂閱者這兩者間是無狀態的,根據產品實際場景需要,可以自己實現單機單點的發布訂閱,也可選擇使用目前流行的分布式消息中間件:
RabbitMQ、ActiveMQ、RocketMQ、kafka等
二、觀察者與訂閱者的區別
觀察者與業務主體是耦合的,並且是即時通知的;訂閱者與業務主體完全解耦,只通過中間的信息通道通知,互相不知道對方的存在,可以是同步也可以是異步
三、具體實現
本文主要講解單點模式,有需要隨時可以擴展為分布式方案
發布者接口:
1 namespace Xms.Event.Abstractions 2 { 3 /// <summary> 4 /// 事件發布接口 5 /// </summary> 6 public interface IEventPublisher 7 { 8 /// <summary> 9 /// 發布事件 10 /// </summary> 11 /// <typeparam name="TEvent">事件類型</typeparam> 12 /// <param name="e"></param> 13 void Publish<TEvent>(TEvent e); 14 } 15 }
發布者實現:
1 using System; 2 using System.Linq; 3 using Xms.Event.Abstractions; 4 using Xms.Infrastructure.Inject; 5 using Xms.Logging.AppLog; 6 7 namespace Xms.Event 8 { 9 /// <summary> 10 /// 事件發布者 11 /// </summary> 12 public class EventPublisher : IEventPublisher 13 { 14 private readonly ILogService _logService; 15 private readonly IServiceResolver _serviceResolver; 16 17 public EventPublisher(ILogService logService 18 , IServiceResolver serviceResolver) 19 { 20 _logService = logService; 21 _serviceResolver = serviceResolver; 22 } 23 24 #region Methods 25 26 /// <summary> 27 /// 發布事件 28 /// </summary> 29 /// <typeparam name="TEvent">事件類</typeparam> 30 /// <param name="e">事件對象</param> 31 public virtual void Publish<TEvent>(TEvent e) 32 { 33 //獲取所有事件接收者 34 var consumers = _serviceResolver.GetAll<IConsumer<TEvent>>().ToList(); 35 foreach (var consumer in consumers) 36 { 37 try 38 { 39 //處理事件 40 consumer.HandleEvent(e); 41 } 42 catch (Exception exception) 43 { 44 _logService.Error(exception); 45 } 46 } 47 } 48 49 #endregion Methods 50 } 51 }
訂閱(消費)者接口:
1 namespace Xms.Event.Abstractions 2 { 3 /// <summary> 4 /// 事件接收接口 5 /// </summary> 6 /// <typeparam name="T"></typeparam> 7 public interface IConsumer<T> 8 { 9 /// <summary> 10 /// 處理事件 11 /// </summary> 12 /// <param name="eventMessage">事件</param> 13 void HandleEvent(T eventMessage); 14 } 15 }
事件(消息):
這里只給出一個作為示例,實際上一般會有記錄的:“創建”、“修改”、“刪除”,流程相關的:“發起審批”、“審批通過”、“審批完成”等等
1 namespace Xms.Flow.Core.Events 2 { 3 /// <summary> 4 /// 工作流啟動后事件 5 /// </summary> 6 public class WorkFlowStartedEvent 7 { 8 public WorkFlowStartUpContext Context { get; set; } 9 public WorkFlowExecutionResult Result { get; set; } 10 } 11 }
服務注冊:
詳細實現回看.netcore之DI批量注入(支持泛型)
1 using Microsoft.Extensions.Configuration; 2 using Microsoft.Extensions.DependencyInjection; 3 using Xms.Core; 4 using Xms.Infrastructure.Inject; 5 6 namespace Xms.Event 7 { 8 /// <summary> 9 /// 事件模塊服務注冊 10 /// </summary> 11 public class ServiceRegistrar : IServiceRegistrar 12 { 13 public int Order => 1; 14 15 public void Add(IServiceCollection services, IConfiguration configuration) 16 { 17 //event publisher 18 services.AddScoped<Event.Abstractions.IEventPublisher, Event.EventPublisher>(); 19 //event consumers 20 services.RegisterScope(typeof(Event.Abstractions.IConsumer<>)); 21 } 22 } 23 }
四、應用場景
比如在工作流啟動審批后發送通知
1 using System.Collections.Generic; 2 using Xms.Context; 3 using Xms.Event.Abstractions; 4 using Xms.Flow.Core.Events; 5 using Xms.Infrastructure.Utility; 6 using Xms.Localization.Abstractions; 7 using Xms.Notify.Abstractions; 8 using Xms.Notify.Internal; 9 10 namespace Xms.EventConsumers.Notify 11 { 12 /// <summary> 13 /// 工作流啟動審批后發送通知 14 /// </summary> 15 public class WorkflowStartedNotify : IConsumer<WorkFlowStartedEvent> 16 { 17 private readonly IAppContext _appContext; 18 private readonly ILocalizedTextProvider _loc; 19 private readonly IEnumerable<INotify> _notifies; 20 21 public WorkflowStartedNotify(IAppContext appContext 22 , IEnumerable<INotify> notifies) 23 { 24 _appContext = appContext; 25 _loc = _appContext.GetFeature<ILocalizedTextProvider>(); 26 _notifies = notifies; 27 } 28 public void HandleEvent(WorkFlowStartedEvent eventMessage) 29 { 30 //當前節點處理人 31 foreach (var handlerId in eventMessage.Result.NextHandlerId) 32 { 33 //通知方式:微信、短信、郵件、系統消息等 34 var msg = _loc["workflow_newtasknotify"].FormatWith(eventMessage.Context.EntityMetaData.LocalizedName); 35 //發送消息 36 foreach (var notifier in _notifies) 37 { 38 notifier.Send(new InternalNotifyBody() 39 { 40 TypeCode = 2 41 , 42 Subject = msg 43 , 44 Content = "到你審批了,快到碗里來" 45 , 46 ToUserId = handlerId 47 , 48 LinkTo = "/entity/create?entityid=" + eventMessage.Context.EntityMetaData.EntityId + "&recordid=" + eventMessage.Context.ObjectId 49 }); 50 } 51 } 52 } 53 } 54 }
四、總結
前面講解了訂閱者模式的基本概念及與觀察者的區別,后面展示了具體實現及實際應用場景,大家記住一點就行,這些設計模式最終都是為了達到解藕的目的,要查看完整代碼,請回到這一章