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

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

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

      在微服務中使用領域事件

      稍微回想一下計算機硬件的工作原理我們便不難發現,整個計算機的工作過程其實就是一個對事件的處理過程。當你點擊鼠標、敲擊鍵盤或者插上U盤時,計算機便以中斷的形式處理各種外部事件。在軟件開發領域,事件驅動架構(Event Driven Architecture,EDA)早已被開發者用于各種實踐,典型的應用場景比如瀏覽器對用戶輸入的處理、消息機制以及SOA。最近幾年重新進入開發者視野的響應式編程(Reactive Programming)更是將事件作為該編程模型中的一等公民。可見,“事件”這個概念一直在計算機科學領域中扮演著重要的角色。 
       

       

       
      認識領域事件 
      領域事件(Domain Events)是領域驅動設計(Domain Driven Design,DDD)中的一個概念,用于捕獲我們所建模的領域中所發生過的事情。領域事件本身也作為通用語言(Ubiquitous Language)的一部分成為包括領域專家在內的所有項目成員的交流用語。比如,在用戶注冊過程中,我們可能會說“當用戶注冊成功之后,發送一封歡迎郵件給客戶。”,此時的“用戶已經注冊”便是一個領域事件。 
       
      當然,并不是所有發生過的事情都可以成為領域事件。一個領域事件必須對業務有價值,有助于形成完整的業務閉環,也即一個領域事件將導致進一步的業務操作。舉個咖啡廳建模的例子,當客戶來到前臺時將產生“客戶已到達”的事件,如果你關注的是客戶接待,比如需要為客戶預留位置等,那么此時的“客戶已到達”便是一個典型的領域事件,因為它將用于觸發下一步——“預留位置”操作;但是如果你建模的是咖啡結賬系統,那么此時的“客戶已到達”便沒有多大存在的必要——你不可能在用戶到達時就立即向客戶要錢對吧,而”客戶已下單“才是對結賬系統有用的事件。 
       
      微服務(Microservices)架構實踐中,人們大量地借用了DDD中的概念和技術,比如一個微服務應該對應DDD中的一個限界上下文(Bounded Context);在微服務設計中應該首先識別出DDD中的聚合根(Aggregate Root);還有在微服務之間集成時采用DDD中的防腐層(Anti-Corruption Layer, ACL);我們甚至可以說DDD和微服務有著天生的默契。更多有關DDD的內容,請參考筆者的另一篇文章或參考《領域驅動設計》《實現領域驅動設計》。 
       
      在DDD中有一條原則:一個業務用例對應一個事務,一個事務對應一個聚合根,也即在一次事務中,只能對一個聚合根進行操作。但是在實際應用中,我們經常發現一個用例需要修改多個聚合根的情況,并且不同的聚合根還處于不同的限界上下文中。比如,當你在電商網站上買了東西之后,你的積分會相應增加。這里的購買行為可能被建模為一個訂單(Order)對象,而積分可以建模成賬戶(Account)對象的某個屬性,訂單和賬戶均為聚合根,并且分別屬于訂單系統和賬戶系統。顯然,我們需要在訂單和積分之間維護數據一致性,然而在同一個事務中同時更新兩者又違背了DDD設計原則,并且此時需要在兩個不同的系統之間采用重量級的分布式事務(Distributed Transactioin,也叫XA事務或者全局事務)。另外,這種方式還在訂單系統和賬戶系統之間產生了強耦合。通過引入領域事件,我們可以很好地解決上述問題。 
       
      總的來說,領域事件給我們帶來以下好處: 
      1. 解耦微服務(限界上下文)
      1. 幫助我們深入理解領域模型
      1. 提供審計和報告的數據來源
      1. 邁向事件(Event Sourcing)和CQRS
       
      還是以上面的電商網站為例,當用戶下單之后,訂單系統將發出一個“用戶已下單”的領域事件,并發布到消息系統中,此時下單便完成了。賬戶系統訂閱了消息系統中的“用戶已下單”事件,當事件到達時進行處理,提取事件中的訂單信息,再調用自身的積分引擎(也有可能是另一個微服務)計算積分,最后更新用戶積分。可以看到,此時的訂單系統在發送了事件之后,整個用例操作便結束了,根本不用關心是誰收到了事件或者對事件做了什么處理。事件的消費方可以是賬戶系統,也可以是任何一個對事件感興趣的第三方,比如物流系統。由此,各個微服務之間的耦合關系便解開了。值得注意的一點是,此時各個微服務之間不再是強一致性,而是基于事件的最終一致性。 
       

       

       
       
      事件風暴(Event Storming) 
      事件風暴是一項團隊活動,旨在通過領域事件識別出聚合根,進而劃分微服務的限界上下文。在活動中,團隊先通過頭腦風暴的形式羅列出領域中所有的領域事件,整合之后形成最終的領域事件集合,然后對于每一個事件,標注出導致該事件的命令(Command),再然后為每個事件標注出命令發起方的角色,命令可以是用戶發起,也可以是第三方系統調用或者是定時器觸發等。最后對事件進行分類整理出聚合根以及限界上下文。事件風暴還有一個額外的好處是可以加深參與人員對領域的認識。需要注意的是,在事件風暴活動中,領域專家是必須在場的。更多有關事件風暴的內容,請參考這里。 
       
       

       

       
       
       
      創建領域事件 
      領域事件應該回答“什么人什么時候做了什么事情”這樣的問題,在實際編碼中,可以考慮采用層超類型(Layer Supertype)來包含事件的某些共有屬性: 
       
      public abstract class Event {
          private final UUID id;
          private final DateTime createdTime;
      
          public Event() {
              this.id = UUID.randomUUID();
              this.createdTime = new DateTime();
          }
      }

       

      可以看到,領域事件還包含了ID,但是該ID并不是實體(Entity)層面的ID概念,而是主要用于事件追溯和日志。另外,由于領域事件描述的是過去發生的事情,我們應該將領域事件建模成不可變的(Immutable)。從DDD概念上講,領域事件更像一種特殊的值對象(Value Object)。對于上文中提到的咖啡廳例子,創建“客戶已到達”事件如下: 
       
      public final class CustomerArrivedEvent extends Event {
          private final int customerNumber;
      
          public CustomerArrivedEvent(int customerNumber) {
              super();
              this.customerNumber = customerNumber;
          }
      }
       
      在這個CustomerArrivedEvent事件中,除了繼承自Event的屬性外,還自定義了一個與該事件密切關聯的業務屬性——客戶人數(customerNumber)——這樣后續操作便可預留相應數目的座位了。另外,我們將所有屬性以及CustomerArrivedEvent本身都聲明成了final,并且不向外暴露任何可能修改這些屬性的方法,這樣便保證了事件的不變性。
       
       
      發布領域事件 
      在使用領域事件時,我們通常采用“發布-訂閱”的方式來集成不同的模塊或系統。在單個微服務內部,我們可以使用領域事件來集成不同的功能組件,比如在上文中提到的“用戶注冊之后向用戶發送歡迎郵件”的例子中,注冊組件發出一個事件,郵件發送組件接收到該事件后向用戶發送郵件。 
       

       

       
       
      在微服務內部使用領域事件時,我們不一定非得引入消息中間件(比如ActiveMQ等)。還是以上面的“注冊后發送歡迎郵件”為例,注冊行為和發送郵件行為雖然通過領域事件集成,但是他們依然發生在同一個線程中,并且是同步的。另外需要注意的是,在限界上下文之內使用領域事件時,我們依然需要遵循“一個事務只更新一個聚合根”的原則,違反之往往意味著我們對聚合根的拆分是錯的。即便確實存在這樣的情況,也應該通過異步的方式(此時需要引入消息中間件)對不同的聚合根采用不同的事務,此時可以考慮使用后臺任務。
       
      除了用于微服務的內部,領域事件更多的是被用于集成不同的微服務,如上文中的“電商訂單”例子。 
       

       

       
       
      通常,領域事件產生于領域對象中,或者更準確的說是產生于聚合根中。在具體編碼實現時,有多種方式可用于發布領域事件。 
       
      一種直接的方式是在聚合根中直接調用發布事件的Service對象。以上文中的“電商訂單”為例,當創建訂單時,發布“訂單已創建”領域事件。此時可以考慮在訂單對象的構造函數中發布事件: 
       
      public class Order {
          public Order(EventPublisher eventPublisher) {
              //create order        
              //
              eventPublisher.publish(new OrderPlacedEvent());    
              }
      }
       
       
      注:為了把焦點集中在事件發布上,我們對Order對象做了簡化,Order對象本身在實際編碼中不具備參考性。 
       
      可以看到,為了發布OrderPlacedEvent事件,我們需要將Service對象EventPublisher傳入,這顯然是一種API污染,即Order作為一個領域對象只需要關注和業務相關的數據,而不是諸如EventPublisher這樣的基礎設施對象。 另一種方法是由NServiceBus的創始人Udi Dahan提出來的,即在領域對象中通過調用EventPublisher上的靜態方法發布領域事件:
       
      public class Order {
          public Order() {
              //create order
              //...
              EventPublisher.publish(new OrderPlacedEvent());
          }
      }
       
      這種方法雖然避免了API污染,但是這里的publish()靜態方法將產生副作用,對Order對象的測試帶來了難處。此時,我們可以采用“在聚合根中臨時保存領域事件”的方式予以改進:
       
      public class Order {
      
          private List<Event> events;
      
          public Order() {
              //create order
              //...
              events.add(new OrderPlacedEvent());
          }
      
          public List<Event> getEvents() {
              return events;
          }
      
          public void clearEvents() {
              events.clear();
      
          }
      } 
       
      在測試Order對象時,我們便你可以通過驗證events集合保證Order對象在創建時的確發布了OrderPlacedEvent事件:
       
      @Test
      public void shouldPublishEventWhenCreateOrder() {
          Order order = new Order();
          List<Event> events = order.getEvents();
          assertEquals(1, events.size());
          Event event = events.get(0);
          assertTrue(event instanceof OrderPlacedEvent);
      } 
       
      在這種方式中,聚合根對領域事件的保存只能是臨時的,在對該聚合根操作完成之后,我們應該將領域事件發布出去并及時清空events集合。可以考慮在持久化聚合根時進行這樣的操作,在DDD中即為資源庫(Repository):
       
      public class OrderRepository {
          private EventPublisher eventPublisher;
      
          public void save(Order order) {
              //save the order
              //...
              List<Event> events = order.getEvents();
              events.forEach(event -> eventPublisher.publish(event));
              order.clearEvents();
          }
      }
       
      除此之外,還有一種與“臨時保存領域事件”相似的做法是“在聚合根方法中直接返回領域事件”,然后在Repository中進行發布。這種方式依然有很好的可測性,并且開發人員不用手動清空先前的事件集合,不過還是得記住在Repository中將事件發布出去。另外,這種方式不適合創建聚合根的場景,因為此時的創建過程既要返回聚合根本身,又要返回領域事件。
       
       這種方式也有不好的地方,比如它要求開發人員在每次更新聚合根時都必須記得清空events集合,忘記這么做將為程序帶來嚴重的bug。不過雖然如此,這依然是筆者比較推薦的方式。 
       
      業務操作和事件發布的原子性 
      雖然在不同聚合根之間我們采用了基于領域事件的最終一致性,但是在業務操作和事件發布之間我們依然需要采用強一致性,也即這兩者的發生應該是原子的,要么全部成功,要么全部失敗,否則最終一致性根本無從談起。以上文中“訂單積分”為例,如果客戶下單成功,但是事件發送失敗,下游的賬戶系統便拿不到事件,導致最終客戶的積分并不增加。 
       
      要保證業務操作和事件發布之間的原子性,最直接的方法便是采用XA事務,比如Java中的JTA,這種方式由于其重量級并不被人們所看好。但是,對于一些對性能要求不那么高的系統,這種方式未嘗不是一個選擇。一些開發框架已經能夠支持獨立于應用服務器的XA事務管理器(如Atomikos 和Bitronix),比如Spring Boot作為一個微服務框架便提供了對Atomikos和Bitronix的支持。 
       
      如果JTA不是你的選項,那么可以考慮采用事件表的方式。這種方式首先將事件保存到聚合根所在的數據庫中,由于事件表和聚合根表同屬一個數據庫,整個過程只需要一個本地事務就能完成。然后,在一個單獨的后臺任務中讀取事件表中未發布的事件,再將事件發布到消息中間件中。 
       

       

       
       
      這種方式需要注意兩個問題,第一個是由于發布了事件之后需要將表中的事件標記成“已發布”狀態,即依然涉及到對數據庫的操作,因此發布事件和標記“已發布”之間需要原子性。當然,此時依舊可以采用XA事務,但是這違背了采用事件表的初衷。一種解決方法是將事件的消費方創建成冪等的,即消費方可以多次消費同一個事件。這個過程大致為:整個過程中事件發送和數據庫更新采用各自的事務管理,此時有可能發生的情況是事件發送成功而數據庫更新失敗,這樣在下一次事件發布操作中,由于先前發布過的事件在數據庫中依然是“未發布”狀態,該事件將被重新發布到消息系統中,導致事件重復,但由于事件的消費方是冪等的,因此事件重復不會存在問題。 
       
      另外一個需要注意的問題是持久化機制的選擇。其實對于DDD中的聚合根來說,NoSQL是相比于關系型數據庫更合適的選擇,比如用MongoDB的Document保存聚合根便是種很自然的方式。但是多數NoSQL是不支持ACID的,也就是說不能保證聚合更新和事件發布之間的原子性。還好,關系型數據庫也在向NoSQL方向發展,比如新版本的PostgreSQL(版本9.4)和MySQL(版本5.7)已經能夠提供具備NoSQL特征的JSON存儲和基于JSON的查詢。此時,我們可以考慮將聚合根序列化成JSON格式的數據進行保存,從而避免了使用重量級的ORM工具,又可以在多個數據之間保證ACID,何樂而不為? 
       
      總結
      領域事件主要用于解耦微服務,此時各個微服務之間將形成最終一致性。事件風暴活動有助于我們對微服務進行拆分,并且有助于我們深入了解某個領域。領域事件作為已經發生過的歷史數據,在建模時應該將其創建為不可變的特殊值對象。存在多種方式用于發布領域事件,其中“在聚合中臨時保存領域事件”的方式是值得推崇的。另外,我們需要考慮到聚合更新和事件發布之間的原子性,可以考慮使用XA事務或者采用單獨的事件表。為了避免事件重復帶來的問題,最好的方式是將事件的消費方創建為冪等的。 

       

      posted @ 2017-04-18 22:19  無知者云  閱讀(19830)  評論(29)    收藏  舉報
      主站蜘蛛池模板: 亚洲无人区一区二区三区| 精品九九人人做人人爱| 伦理片午夜视频在线观看| 精品一区二区成人码动漫| 狂躁女人双腿流白色液体| 亚洲国产精品综合久久20| 暖暖影院日本高清...免费| 免费无码成人AV在线播放不卡 | 丁香五月亚洲综合深深爱| 国产精品一起草在线观看| 黑人巨大av无码专区| 亚洲嫩模喷白浆在线观看| 人成午夜大片免费视频77777| 国产成人久久精品流白浆| 溆浦县| 中文在线天堂中文在线天堂| 中文字幕人妻无码一夲道| 精品久久8x国产免费观看| 国产精品日韩中文字幕熟女| 99久久婷婷国产综合精品青草漫画 | 真实单亲乱l仑对白视频| 中文字幕日本一区二区在线观看| 国产精自产拍久久久久久蜜| 亚洲AV美女在线播放啊| 威海市| 久久精品女人的天堂av| 亚洲精品天堂一区二区| 好吊妞视频这里有精品| 国产成人自拍小视频在线| 亚亚洲视频一区二区三区| 国产成人精品一区二区秒拍1o| 国产欧美日韩精品丝袜高跟鞋| 国产一区二区三区尤物视频| 亚洲色最新高清AV网站| 日韩精品一区二区蜜臀av| 国产无遮挡裸体免费久久| 精品午夜福利短视频一区| 在国产线视频A在线视频| 国产短视频一区二区三区| 人妻放荡乱h文| 国产精品美女久久久久久麻豆 |