<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      CQRS實踐(2): Command的實現

      CQRS

      概述

      繼續引用上篇文章中的圖片(來源于Udi Dahan博客),UI中的寫入操作都將被封裝為一個命令中,發送給Domain Model來處理。

      我們遵循Domain Driven Design的設計思想,因此所有的業務邏輯都只在Domain Model中處理,Command中將不會帶有業務邏輯。Command中的代碼無非是通過Repository獲取某些個聚合根(Aggregate Root),然后將操作委托給相應的領域對象或領域服務來處理,僅此而已。

      實現

      實現上,我們會涉及三個東西:

      (1) Command對象

      Command對象的作用是用來封裝命令數據,所以這類對象以屬性為主,少量簡單方法,但注意這些方法中不能包含業務邏輯。

      舉個用戶注冊的例子,用戶注冊是一個命令,所以我們需要一個RegisterCommand類,這個類定義如下:

      public class RegisterCommand : ICommand
      {
      public string Email { get; set; }

      public string NickName { get; set; }

      public string Password { get; set; }

      public string ConfirmPassword { get; set; }

      public Gender Gender { get; set; }

      public RegisterCommand()
      {
      }
      }


      這個類的每個屬性基本上都對應著注冊表單中的一個輸入(為了方便起見,上面的每個屬性都是public set,但若屬性不多不影響編碼,最好把屬性都改成private set,然后將屬性的值通過構造函數傳入)。當用戶點擊“注冊”按鈕時,Controller(假設使用MVC作為表現層模式)中會創建一個RegisterCommand的實例,設置相應的值,然后調用CommandBus.Send(registerCommand),然后根據執行的情況顯示相應的信息給用戶。(CommandBus后面會講到)

      (2) CommandExecutor

      CommandExecutor的作用是執行一個命令,對于注冊的例子,我們會有一個RegisterCommandExecutor的類,它只有一個Execute方法,接受RegisterCommand參數:

      public class RegisterCommandExecutor : ICommandExecutor<RegisterCommand>
      {
      private IRepository<User> _repository;

      public RegisterCommandExecutor(IRepository<User> repository)
      {
      _repository = repository;
      }

      public void Execute(RegisterCommand cmd)
      {
      if (String.IsNullOrEmpty(cmd.Email))
      throw new InvalidOperationException("Email is required.");

      if (cmd.Password != cmd.ConfirmPassword)
      throw new InvalidOperationException("Password not match.");

      // other "Command parameter" validations

      var service = new RegistrationService(_repository);
      service.Register(cmd.Email, cmd.NickName, cmd.Password, cmd.Gender);
      }
      }

      在Execute方法中,我們需要先驗證Command的正確性,但需要注意的是,這里的驗證只是驗證RegisterCommand中的數據是否合法,并非驗證業務邏輯。例如,這里會驗證郵箱是否為空且格式是否正確,但郵箱格式正確并不意味著就可以注冊,因為系統可能要求18歲以上的成年人才能注冊,而這屬于業務邏輯,RegistrationService將會負責確保所有的業務規則不被破壞,RegistrationService屬于Domain Service,存在于Domain Model中。

      可以看到,CommandExecutor中主要有兩部分工作,一是驗證傳入的Command對象是否合法,二是調用領域模型完成操作。上一篇文章中提到的Command是一個概念層次的Command,它不單指(1)中的Command,而是包含了(1)和(2)等。

      PS: 記得三四年前糾結于“三層架構”的時候,最搞不懂的應該算是“業務邏輯”了,現在似乎有點領悟。“業務邏輯”中關鍵的詞是“業務”,這也是它和其它邏輯如應用邏輯區分開來的關鍵因素,如果一個邏輯帶有“業務價值”,那它就算“業務”邏輯,否則就不算。比如下訂單時,如果客戶的退款次數超過100,那就不允許下單,這是業務邏輯;而"注冊時兩次輸入的密碼必須一致"則不算業務邏輯。但我仍有個問題,要求Email必須唯一算不算業務邏輯呢?我個人傾向于認為它是業務邏輯。那郵箱格式必須正確(即中間必須有@符號等等)算業務邏輯嗎?個人傾向于認為是不算,如果不算業務邏輯,領域模型中需要對其進行驗證嗎?個人傾向于不用在領域模型中驗證,這些邏輯應該在CommandExecutor中進行驗證。不知道大家的看法如何?

      (3) Command Bus

      用于執行Command的是CommandExecutor,但CommandExecutor卻并不用來在UI層調用,UI層中只會用到Command對象和即將提到的Command Bus。Command Bus的作用是將一個Command派發給相應的CommandExecutor去執行。在開發UI層時,我們不需要關心Command會被哪個Executor執行了,而只要知道,上帝賜予了我們一個CommandBus,我們只要創建好Command對象,扔給它,神奇的CommandBus就會幫我們把它執行完。這樣一來,對于UI層的開發來說,所涉及的概念很簡單,涉及的類也少,大部分的工作都是得到表單中的輸入,封裝成Command對象,扔給CommandBus。

      下面是注冊的例子的Controller:

      public class AccountController : Controller 
      {
      [HttpPost]
      public ActionResult Register(RegisterCommand command)
      {
      if (ModelState.IsValid)
      {
      try
      {
      CommandBus.Execute(command);
      FormsAuthentication.SetAuthCookie(command.Email, false);

      return RedirectToAction("Index", "Home");
      }
      catch (Exception ex)
      {
      ModelState.AddModelError("Error", ex);
      }
      }

      return View(command);
      }
      }


      CommandBus的實現也很簡單。首先,我們需要讓CommandExecutor都實現一個泛型接口:

      public interface ICommandExecutor<TCommand>
      where TCommand : ICommand
      {
      void Execute(TCommand cmd);
      }

      其中ICommand是一個空接口,沒有任何方法(即Marker Interface),它的作用是實現編譯時約束,這樣我們可以限制傳入CommandExecutor的都是Command對象,而不是不小心傳錯的User對象(所有的Command對象都必須實現ICommand接口)。

      然后,把CommandBus寫成這樣:

      public static class CommandBus
      {
      public static void Send<TCommand>(TCommand cmd) where TCommand : ICommand {
      var type = typeof(TCommand);
      var executorType = FindExecutorType(type);
      var executor = Activator.CreateInstance(executorType);
      executor.Executor(cmd);
      }
      }

      在這個Send方法中,我們通過反射獲取到泛型參數為傳入的Command對象的具體類型的Executor類,再調用其Execute方法即可。上面的代碼是偽代碼,實際實現中我們可以通過IoC框架來簡化這個過程,另外也可以做一些改進,例如將CommandBus設計為擴展點之一。另外我們還可以將UnitOfWork(相當于平常的EntityFramework中的IDbContext,Linq 2 SQL中的DataContext)的生命周期在CommandBus中進行控制。

      比較完整的CommandBus代碼如下(仍有小部分偽代碼):

      public interface ICommandBus
      {
      void Execute<TCommand>(TCommand cmd) where TCommand : ICommand;
      }
      public class DefaultCommandBus : ICommandBus
      {
      public void Send<TCommand>(TCommand cmd) where TCommand : ICommand
      {
      UnitOfWorkContext.StartUnitOfWork();

      var executor = ObjectContainer.Resolve<ICommandExecutor<TCommand>>();
      executor.Execute(cmd);

      UnitOfWorkContext.Commit();
      }
      }

      其它的代碼不貼在文章中,所有代碼可以文末處下載。

      這樣我們就完成了CQRS中Command的一個基本實現。

      一些注意點

      (1) Command表示想要執行的命令,所以Command類的類名應當是動詞的形式。例如RegisterCommand, ChangePasswordCommand等。不過Command后綴則是可選的,只要能保持一致即可。

      (2) Command和CommandExecutor是一一對應的。也就是說,一個Command只會對應一個CommandExecutor,這和后面的事件有區別,事件是一對多的,一個Event可以對應多個EventHandler。

      (3) 從文中的AccountController的Register Action中可以看到,Command對象也起到了DTO(Data Transfer Object,在這個例子中感覺稱作View Model也無妨)的作用,這也是把Command和Executor相分離,不把Execute方法直接寫在Command類中的原因之一。

      (4) 注意Command的類名的重要作用,每個Command類的名稱都清晰地表達了一個意圖,例如ChangePasswordCommand清晰的表達了這個命令是要修改密碼,所以千萬不要隨意"復用"Command,這里的“復用”指的是,看到某兩個Command中有完全一樣的屬性,就覺得沒有必要使用兩個Command,而把它們合并成一個Command,這樣的"復用"會讓系統變得越來越難以理解,雖然它可能的確減少了幾行代碼。

      (5) 命令通常是用“發送”來描述,而事件則是用“發布”來描述,所以CommandBus中的方法名稱個人認為應該用Send比較合適,而不用Publish之類的。 

      代碼下載

      https://files.cnblogs.com/mouhong-lin/CQRS.zip

      說明:下載的代碼和文章中的代碼不完全一致,但也不會有太大差別。示例代碼中只實現了Command和用戶注冊功能,其它的如事件之類皆未包含。

       

      PS: 關于技術文章的寫作,我最怕的是自己的理解有偏差,以致于造成不好的影響,但不寫又沒有討論。今晚突然想到一個自我感覺比較不錯的建議:有興趣的童鞋在閱讀的過程中,若感覺某句或某觀點不準確,可以以評論的形式提出,之后作者以不刪原句的形式進行修改(將原句子用刪除線劃掉),這樣既可以讓文章變得更嚴謹,同時也會清楚的看到哪些觀點經過了什么樣的修正。

      posted @ 2012-03-28 09:01  水言木  閱讀(11475)  評論(8)    收藏  舉報
      主站蜘蛛池模板: 国产成人剧情AV麻豆果冻| 亚洲欧美在线观看品| 国产熟睡乱子伦午夜视频| 97se亚洲综合在线天天| 蜜臀av黑人亚洲精品| 国产亚洲欧洲av综合一区二区三区 | 国产AV无码专区亚洲AV紧身裤| 国产成人精品一区二区三区免费| 国产精品美人久久久久久AV| 欧美粗大猛烈老熟妇| 人人做人人爽人人爱| 色综合色综合色综合久久| 深田えいみ禁欲后被隔壁人妻| 国内精品久久久久影院网站| 国产精品毛片一区视频播| 国产亚洲精品在天天在线麻豆 | 国产性一交一乱一伦一色一情 | 亚洲色欲色欲www| 精品国产粉嫩一区二区三区| av午夜福利一片免费看久久| 大地资源中文第三页| 最新国产AV最新国产在钱| 欧美日韩精品一区二区三区高清视频| 漂亮人妻被黑人久久精品| 久久大香线蕉国产精品免费| 亚洲中文字幕一区二区| 国产精品普通话国语对白露脸| 噶尔县| 国产欧美亚洲精品第1页| 国产成人精彩在线视频50| 天堂V亚洲国产V第一次| 精品视频一区二区三区不卡| 亚洲国产精品无码av| 国产成人精品亚洲高清在线| 久久综合综合久久综合| 亚洲av成人一区在线| 国产精品色内内在线播放| 国精产品一品二品国精在线观看| 特级毛片a片久久久久久| 无码天堂亚洲国产AV| 波多野结衣乳喷高潮视频 |