亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

ASP.NET Core實(shí)現(xiàn)單體程序的事件發(fā)布/訂閱詳解

 更新時(shí)間:2019年03月04日 10:45:51   作者:Lamond Lu  
這篇文章主要給大家介紹了關(guān)于ASP.NET Core實(shí)現(xiàn)單體程序的事件發(fā)布/訂閱的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

背景

事件發(fā)布/訂閱是一種非常強(qiáng)大的模式,它可以幫助業(yè)務(wù)組件間實(shí)現(xiàn)完全解耦,不同的業(yè)務(wù)組件只依賴事件,只關(guān)注哪些事件是需要自己處理的,而不用關(guān)注誰(shuí)來(lái)處理自己發(fā)布事件,事件追溯(Event Sourcing)也是基于事件發(fā)布/訂閱的。在微服務(wù)架構(gòu)中,事件發(fā)布/訂閱有非常多的應(yīng)用場(chǎng)景。今天我給大家分享一個(gè)基于ASP.NET Core的單體程序使用事件發(fā)布/訂閱的例子,針對(duì)分布式項(xiàng)目的事件發(fā)布/訂閱比較復(fù)雜,難點(diǎn)是事務(wù)處理,后續(xù)我會(huì)另寫一篇博文來(lái)演示。

案例說(shuō)明

當(dāng)前我們有一個(gè)基于ASP.NET Core的電子商務(wù)系統(tǒng),在項(xiàng)目的初期,業(yè)務(wù)非常簡(jiǎn)單,只有一個(gè)購(gòu)物車模塊和一個(gè)訂單模塊,所有的代碼都放在一個(gè)項(xiàng)目中。

整個(gè)項(xiàng)目使用了一個(gè)簡(jiǎn)單的三層架構(gòu)。

這里當(dāng)用戶提交購(gòu)物車的時(shí)候,程序會(huì)在ShoppingCartManager類的SubmitShoppingCart方法中執(zhí)行3個(gè)操作

  • 修改當(dāng)前購(gòu)物車的狀態(tài)為完成
  • 根據(jù)購(gòu)物車中的物品創(chuàng)建一個(gè)新訂單
  • 給用戶發(fā)郵件

代碼如下:

 public void SubmitShoppingCart(string shoppingCartId)
 {
 var shoppingCart = _unitOfWork.ShoppingCartRepository
 .GetShoppingCart(shoppingCartId);

 _unitOfWork.ShoppingCartRepository
 .SubmitShoppingCart(shoppingCartId);

 _unitOfWork.OrderRepository
  .CreatOrder(new CreateOrderDTO
  {
  Items = shoppingCart.Items
   .Select(p => new NewOrderItemDTO
    {
    ItemId = p.ItemId,
    Name = p.Name,
    Price = p.Price
   }).ToList()
  });

 //這里為了簡(jiǎn)化代碼,我用命令行表示發(fā)送郵件的邏輯
 Console.WriteLine("Confirm Email Sent.");

 _unitOfWork.Save();
 }

根據(jù)SOLID設(shè)計(jì)原則中的單一責(zé)任原則,如果一個(gè)類承擔(dān)的職責(zé)過(guò)多,就等于把這些職責(zé)耦合在一起了。這里生成訂單和發(fā)送郵件都不應(yīng)該是當(dāng)前SubmitShoppingCart需要負(fù)責(zé)的,所以我們需要它們從這個(gè)方法中移出去,使用的方法就是事件訂閱/發(fā)布。

新的架構(gòu)圖

以下是使用事件發(fā)布/訂閱之后的系統(tǒng)架構(gòu)圖。

  • 這里我們會(huì)創(chuàng)建一個(gè)購(gòu)物車提交事件ShoppingCartSubmittedEvent。
  • 當(dāng)站點(diǎn)啟動(dòng)的時(shí)候,我們會(huì)在一個(gè)名為EventHandlerContainer的類中注冊(cè)訂閱ShoppingCartSubmittedEvent事件的2個(gè)處理類CreateOrderHandler和ConfirmEmailSentHandler。
  • 在SubmitShoppingCart方法中,我們會(huì)做2件事情:
          更改當(dāng)前購(gòu)物車的狀態(tài)。
          發(fā)布ShoppingCartSubmittedEvent事件。
  • CreateOrderHandler事件處理器會(huì)調(diào)用OrderManager類中的創(chuàng)建訂單方法。
  • ConfirmEmailSentHandler事件處理器會(huì)負(fù)責(zé)發(fā)送郵件。

好的,下面讓我們來(lái)一步一步實(shí)現(xiàn)以上描述的代碼。

添加事件基類

這里我們首先定義一個(gè)事件基類,其中暫時(shí)只添加了一個(gè)屬性O(shè)ccuredOn,它表示了當(dāng)前事件的觸發(fā)時(shí)間。

 public class EventBase
 {
 public EventBase()
 {
  OccuredOn = DateTime.Now;
 }

 protected DateTime OccuredOn
 {
  get;
  set;
 }
 }

定義購(gòu)物車提交事件

接下來(lái)我們就需要?jiǎng)?chuàng)建購(gòu)物車提交事件類ShoppingCartSubmittedEvent, 它繼承自EventBase, 并提供了一個(gè)購(gòu)物項(xiàng)集合

 public class ShoppingCartSubmittedEvent : EventBase
 {
 public ShoppingCartSubmittedEvent()
 {
  Items = new List<ShoppingCartSubmittedItem>();
 }

 public List<ShoppingCartSubmittedItem> Items { get; set; }
 }

 public class ShoppingCartSubmittedItem
 {
 public string ItemId { get; set; }

 public string Name { get; set; }

 public decimal Price { get; set; }

 }

定義事件處理器接口

為了添加事件處理器,我們首先需要定義一個(gè)泛型接口類IEventHandler

 public interface IEventHandler<T> where T : EventBase
 {
 void Run(T obj);

 Task RunAsync(T obj);
 }

這個(gè)泛型接口類的是泛型類型必須繼承自EventBase類。接口提供了2個(gè)方法Run和RunAsync。 它們定義了該接口的實(shí)現(xiàn)類必須實(shí)現(xiàn)同一個(gè)處理邏輯的同步和異步方法。

為購(gòu)物車提交事件編寫事件處理器

有了事件處理器接口,接下來(lái)我們就可以開(kāi)始為購(gòu)物車提交事件添加事件處理器了。這里我們?yōu)榱藢?shí)現(xiàn)前面定義的邏輯,我們需要?jiǎng)?chuàng)建2個(gè)處理器CreateOrderHandler和ConfirmEmailSentHandler

CreateOrderHandler.cs

 public class CreateOrderHandler : IEventHandler<ShoppingCartSubmittedEvent>
 {
 private IOrderManager _orderManager = null;


 public CreateOrderHandler(IOrderManager orderManager)
 {
  _orderManager = orderManager;
 }

 public void Run(ShoppingCartSubmittedEvent obj)
 {
  _orderManager.CreateNewOrder(new Models.DTOs.CreateOrderDTO
  {
  Items = obj.Items.Select(p => new Models.DTOs.NewOrderItemDTO
  {
   ItemId = p.ItemId,
   Name = p.Name,
   Price = p.Price
  }).ToList()
  });
 }

 public Task RunAsync(ShoppingCartSubmittedEvent obj)
 {
  return Task.Run(() =>
  {
  Run(obj);
  });
 }
 }

代碼解釋:

  • 在CreateOrderHandler的構(gòu)造函數(shù)中,我們注入了IOrderManager接口對(duì)象,CreateNewOrder負(fù)責(zé)最終創(chuàng)建訂單的工作
  • 這里為了簡(jiǎn)化代碼,我直接使用了Task.Run,并在其中調(diào)用了同步方法實(shí)現(xiàn)

ConfirmEmailSentHandler.cs

 public class ConfirmEmailSentHandler : IEventHandler<ShoppingCartSubmittedEvent>
 {
 public void Run(ShoppingCartSubmittedEvent obj)
 {
  Console.WriteLine("Confirm Email Sent.");
 }

 public Task RunAsync(ShoppingCartSubmittedEvent obj)
 {
  return Task.Run(() =>
  {
  Console.WriteLine("Confirm Email Sent.");
  });
 }
 }

代碼解釋:

  • 這個(gè)處理類非常簡(jiǎn)單,為了簡(jiǎn)化代碼,我僅輸出了一行文本來(lái)表示實(shí)際需要運(yùn)行的代碼。

為OrderManager類添加創(chuàng)建訂單方法

IOrderManager.cs

 public interface IOrderManager
 {
  string CreateNewOrder(CreateOrderDTO dto);
 }

OrderManager.cs

 public class OrderManager : IOrderManager
 {
  private IOrderRepository _orderRepository;

  public OrderManager(IOrderRepository orderRepository)
  {
   _orderRepository = orderRepository;
  }

  public string CreateNewOrder(CreateOrderDTO dto)
  {
   var orderId = _orderRepository.CreatOrder(dto);

   Console.WriteLine($"One order created: {JsonConvert.SerializeObject(dto)}");

   return orderId;
  }
 }

創(chuàng)建EventHandlerContainer

下面我們來(lái)編寫最核心的事件處理器容器。在這里我們的事件處理器容器完成了3個(gè)功能

  • 訂閱事件(Subscribe Event)
  • 取消訂閱事件(Unsubscribe Event)
  • 發(fā)布事件(Publish Event)
 public class EventHandlerContainer
 {
  private IServiceProvider _serviceProvider = null;
  private static Dictionary<string, List<Type>> _mappings = new Dictionary<string, List<Type>>();

  public EventHandlerContainer(IServiceProvider serviceProvider)
  {
   _serviceProvider = serviceProvider;
  }

  public static void Subscribe<T, THandler>()
   where T : EventBase
   where THandler : IEventHandler<T>
  {
   var name = typeof(T).Name;

   if (!_mappings.ContainsKey(name))
   {
    _mappings.Add(name, new List<Type> { });
   }

   _mappings[name].Add(typeof(THandler));
  }

  public static void Unsubscribe<T, THandler>()
   where T : EventBase
   where THandler : IEventHandler<T>
  {
   var name = typeof(T).Name;
   _mappings[name].Remove(typeof(THandler));

   if (_mappings[name].Count == 0)
   {
    _mappings.Remove(name);
   }
  }

  public void Publish<T>(T o) where T : EventBase
  {
   var name = typeof(T).Name;

   if (_mappings.ContainsKey(name))
   {
    foreach (var handler in _mappings[name])
    {
     var service = (IEventHandler<T>)_serviceProvider.GetService(handler);

     service.Run(o);
    }
   }
  }

  public async Task PublishAsync<T>(T o) where T : EventBase
  {
   var name = typeof(T).Name;

   if (_mappings.ContainsKey(name))
   {
    foreach (var handler in _mappings[name])
    {
     var service = (IEventHandler<T>)_serviceProvider.GetService(handler);

     await service.RunAsync(o);
    }
   }
  }
 }

代碼解釋:

  • 這里我沒(méi)有直接訂閱事件處理器的實(shí)例,而且訂閱了事件處理器的類型
  • 多個(gè)事件處理器可以訂閱同一個(gè)事件
  • EventHandlerContainer的構(gòu)造函數(shù)中,我們注入了一個(gè)IServiceProvider,我們可以使用它來(lái)獲得對(duì)應(yīng)事件處理器的實(shí)例。

在程序啟動(dòng)時(shí),注冊(cè)事件訂閱

現(xiàn)在我們來(lái)Startup.cs的ConfigureServices方法,這里我們需要進(jìn)行服務(wù)注冊(cè),并完成事件訂閱。

 public void ConfigureServices(IServiceCollection services)
 {
  services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);

  services.AddScoped<IOrderManager, OrderManager>();
  services.AddScoped<IShoppingCartManager, ShoppingCartManager>();
  services.AddScoped<IShoppingCartRepository, ShoppingCartRepository>();
  services.AddScoped<IOrderRepository, OrderRepository>();
  services.AddScoped<IUnitOfWork, UnitOfWork>();
  services.AddScoped<CreateOrderHandler>();
  services.AddScoped<ConfirmEmailSentHandler>();
  services.AddScoped<EventHandlerContainer>();


  EventHandlerContainer.Subscribe<ShoppingCartSubmittedEvent, CreateOrderHandler>();
  EventHandlerContainer.Subscribe<ShoppingCartSubmittedEvent, ConfirmEmailSentHandler>();
 }

注意:這里保證一個(gè)Api請(qǐng)求中的所有數(shù)據(jù)庫(kù)操作在一個(gè)事務(wù)里,這里我們使用Scoped作用域。這樣我們就可以在調(diào)用工作單元IUnitOfWork接口的Save代碼中啟用事務(wù)。

修改ShoppingCartManager

最后我們來(lái)修改ShoppingCartManager, 改用發(fā)布事件的方式來(lái)完成后續(xù)創(chuàng)建訂單和發(fā)送郵件的功能。

 public void SubmitShoppingCart(string shoppingCartId)
 {
  var shoppingCart = _unitOfWork.ShoppingCartRepository
   .GetShoppingCart(shoppingCartId);

  _unitOfWork.ShoppingCartRepository
   .SubmitShoppingCart(shoppingCartId);


  _container.Publish(new ShoppingCartSubmittedEvent()
  {
   Items = shoppingCart
     .Items
     .Select(p => new ShoppingCartSubmittedItem
     {
      ItemId = p.ItemId,
      Name = p.Name,
      Price = p.Price
     })
     .ToList()
  });
  
  _unitOfWork.Save();
 }

這樣ShoppingCartManager就只需要關(guān)注購(gòu)物車狀態(tài)的變更,而不需要關(guān)注發(fā)送確認(rèn)郵件和創(chuàng)建訂單了。

最終效果

現(xiàn)在讓我們啟動(dòng)項(xiàng)目,

首先我們使用[POST] /api/shoppingCarts來(lái)添加一個(gè)新的購(gòu)物車, 這個(gè)API會(huì)返回當(dāng)前購(gòu)物車的Id

 

然后我們使用[PUT] /api/shoppingCarts/ShoppingCart_636872897140555966來(lái)模擬提交購(gòu)物車,程序返回操作成功

 

最后我們查看一下控制臺(tái)的輸出日志

 

2個(gè)事件處理器都被正確觸發(fā)了。

總結(jié)

至此我們的代碼重構(gòu)完成。 最終的代碼中,SubmitShoppingCart方法,僅負(fù)責(zé)修改購(gòu)物車狀態(tài)并發(fā)布一個(gè)購(gòu)物車提交的事件。生成訂單和發(fā)送郵件的功能代碼都被移動(dòng)到了獨(dú)立的處理類中。

這樣的方式的好處不僅僅是完成了代碼的解耦,針對(duì)后續(xù)的擴(kuò)展也非常有利,想想一下,如果在未來(lái)當(dāng)前項(xiàng)目需求追加這樣一個(gè)功能,當(dāng)提交購(gòu)物車的時(shí)候,除了要發(fā)送確認(rèn)郵件,還要發(fā)送手機(jī)短信。這時(shí)候你根本不需要去修改ShoppingCartManager類,你只需要針對(duì)ShoppingCartSubmittedEvent在再添加一個(gè)新的事件處理器即可,這也滿足的SOLID的開(kāi)閉原則。

項(xiàng)目源代碼:https://github.com/lamondlu/EventHandlerInSingleApplication

好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • .net 預(yù)處理指令符的使用詳解

    .net 預(yù)處理指令符的使用詳解

    這篇文章主要介紹了.net 預(yù)處理指令符的使用詳解,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-04-04
  • .NET 解決TabControl 頁(yè)里面多余邊距問(wèn)題經(jīng)驗(yàn)分享

    .NET 解決TabControl 頁(yè)里面多余邊距問(wèn)題經(jīng)驗(yàn)分享

    不知道各位同學(xué)有沒(méi)有遇到在向TabPage添加內(nèi)容后,里面的東西總是填不滿 TabPage,總是有幾個(gè)像素的空白邊距
    2012-04-04
  • ASP.NET學(xué)習(xí)路線圖淺談

    ASP.NET學(xué)習(xí)路線圖淺談

    這篇文章介紹了ASP.NET學(xué)習(xí)路線圖,有需要的朋友可以參考一下
    2013-11-11
  • MVC4制作網(wǎng)站教程第二章 用戶修改資料2.4

    MVC4制作網(wǎng)站教程第二章 用戶修改資料2.4

    這篇文章主要為大家詳細(xì)介紹了MVC4制作網(wǎng)站教程,用戶修改資料功能的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-08-08
  • ASP.NET MVC分頁(yè)問(wèn)題解決

    ASP.NET MVC分頁(yè)問(wèn)題解決

    這篇文章主要為大家詳細(xì)介紹了ASP.NET MVC分頁(yè)問(wèn)題的解決方法,Ajax.Pager分頁(yè)的使用注意事項(xiàng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-01-01
  • ASP.NET記錄錯(cuò)誤日志的實(shí)現(xiàn)方法

    ASP.NET記錄錯(cuò)誤日志的實(shí)現(xiàn)方法

    在本文中,我們將通過(guò)一個(gè)簡(jiǎn)單的處理來(lái)記錄在我們的網(wǎng)站中的錯(cuò)誤和異常
    2013-05-05
  • ASP.NET遞歸法求階乘解決思路

    ASP.NET遞歸法求階乘解決思路

    遞歸就是在過(guò)程或函數(shù)里調(diào)用自身,在使用遞歸策略時(shí),必須有一個(gè)明確的遞歸結(jié)束條件,稱為遞歸出口遞歸算法解題通常顯得很簡(jiǎn)潔,但遞歸算法解題的運(yùn)行效率較低。所以一般不提倡用遞歸算法設(shè)計(jì)程序
    2012-12-12
  • MVC+EasyUI+三層新聞網(wǎng)站建立 分頁(yè)查詢數(shù)據(jù)功能(七)

    MVC+EasyUI+三層新聞網(wǎng)站建立 分頁(yè)查詢數(shù)據(jù)功能(七)

    這篇文章主要為大家詳細(xì)介紹了MVC+EasyUI+三層新聞網(wǎng)站建立的第七篇,教大家如何分頁(yè)查詢出數(shù)據(jù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • .NET Core日志配置的方法

    .NET Core日志配置的方法

    熟悉ASP.NET的開(kāi)發(fā)者一定對(duì)web.config文件不陌生,這篇文章主要介紹了.NET Core日志配置的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-08-08
  • 淺談asp.net Forms身份驗(yàn)證詳解

    淺談asp.net Forms身份驗(yàn)證詳解

    這篇文章主要介紹了淺談asp.net Forms身份驗(yàn)證詳解 ,這種方法可以輕松的保持用戶的登錄狀態(tài)(如果用戶想這樣),便捷的用戶授權(quán)配置,增強(qiáng)的安全性,有興趣的可以了解一下。
    2016-12-12

最新評(píng)論