精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

初探領域驅動設計:為復雜業務而生

開發
領域驅動設計也就是3D(Domain-Driven Design)已經有了10年的歷史,我相信很多人或多或少都聽說過這個名詞,但是有多少人真正懂得如何去運用它,或者把它運用好呢?于是有人說,DDD和TDD這些玩意是一些形而上的東西,只是一茶余飯后的談資,又或是放到簡歷上提升逼格而已。

概述

領域驅動設計也就是3D(Domain-Driven Design)已經有了10年的歷史,我相信很多人或多或少都聽說過這個名詞,但是有多少人真正懂得如何去運用它,或者把它運用好呢?于是有人說,DDD和TDD這些玩意是一些形而上的東西,只是一茶余飯后的談資,又或是放到簡歷上提升逼格而已。前面這句話我寫完之后猶豫了,猶豫要不要把它刪掉,因為它讓我看起來像個噴子,我確實感到不解,為什么別人10年前創造總結出來的東西,我們在10年之后對它的理解還處于這么低的一個層次。開篇就說遠了,我也是最近才開始認真學習領域驅動設計,并且得到了園子里面netfocus,劉標才田園里的蟋蟀的幫助,在此再次表示感謝。希望能和大家一起把DDD普及下去。

我們之前有一個關于領域驅動設計的討論,另外dax.net也有一個關于領域驅動設計的系列寫得不錯,有興趣的同學可以看看。本文會以一個初學者的角度來講解DDD,讓我們一切從零開始,我相信你跟我一樣也會愛上它的。

本篇主要討論一下為什么我們要用DDD,它能夠為我們帶來什么?

一點廢話 ,我們需要好的設計么?

當我們學習一些設計模式或者框架的時候,總有人會站出來和你說“這些都沒有用,只要能實現功能就行了。” 在這里并非針對某個人,實際上我認為他們說的是對的,在資源有限的情況下,我們為了完成項目的交付,這是我們最好的選擇。但是別忘了,欠下的債總是要還的,以實現功能為導向的項目務必會造成維護性的大大降低,如果只是一個臨時隨便用用的東西倒是可以一試,但如果是要長期進行更新的產品,那后期就會拖該產品的后腿。

我們團隊現在維護著一個有著20多年歷史的產品,該產品是一個酒店、餐飲行業的POS系統,在美國和亞太地區都占有著比較大的市場份額。該產品從C,C++,VB6一路更新,直到現在的C#,但是很可惜不是整體替換,而是局部的,所以現在項目里面這4種代碼全都有。可能你會覺得這玩的是混搭,是潮流,但事實是,一旦產品上線之后,會有很多的新功能,老bug等在那里,再加上“重市場輕技術”的高層在那里制訂戰略,你壓根就沒有時間或者沒有多少時間去重構。日積月累,等著你的就是每一次改代碼都如履薄冰,一不小心就因為改一個bug而整出好幾個新bug出來,前不久我們為了新版本的發布就停下所有開發的任務,大家集體花了1個月的時間去做回歸測試了。因為前期發布新版本之后bug太多,所以這次老大們都不敢輕易發布了。:)

這是我們血的教訓,如果你前期只顧開發功能,最后就會讓你很難再開發新功能。所以真誠的希望大家不要再片面的說“只要實現功能就可以了!”,軟件開發的領域這么大,我們沒有必要把自己局限在某一個框框里面。對于大型系統來說,我們要學習的地方還有很多:

  • 組織良好、可閱讀性高的代碼可以讓其它開發人員很容易的開始去修改代碼。
  • 低耦合,高內聚 - 適合運用設計模式以及原則來設計一些好的框架可以降低修改代碼引發新bug的風險。
  • 良好的單元測試以及集成測試可以及時的幫助我們檢測新增或修改的代碼是否會破壞原有的邏輯。
  • 自動化測試絕對是省時省力的好幫手,也是項目質量的保證。
  • 持續集成可以幫助我們更快速安全的進行迭代。

上面說了這么多也沒有提到DDD,那么為什么它能夠在構建復雜系統的時候有優勢呢?我們可以從以下幾個點去思考:

  • 從設計階段出發,站在業務的角度思考問題
  • 厘清業務主次
  • 獨立領域業務層,打通開發和測試階段
  • 干凈的代碼

從設計階段開始,站在業務的角度思考問題

除了DDD,現在還流行另外一個詞匯TDD。但是不知道大家有沒有注意到DDD(Domain-Driven Design)中的D代表著設計,而TDD(Test-Driven Development)中的D代表著開發,你有沒有曾幾何時把領域驅動設計說成領域驅動開發呢?當然我們確實是可以根據領域驅動來開發,但是DDD被設計出來的完美初衷卻是設計。TDD強調的已經是開發了,要求開發人員先寫單元測試然后再通過不斷的迭代重構讓單元測試通過,以此來實現功能。這樣做的好處是強迫讓開發人員清楚正確的理解需求,要知道這年頭沒有正確理解需求就開始寫代碼的程序員大有人在,并且我不認為需求就是業務,需求已經是將本來的業務理解之后,轉化為了通過計算機可以實現的一些功能定義,通常是業務分析師或者項目經理會去完成這個工作。而DDD中的D(領域)更像是本來的業務,所以在領域驅動設計的時候,開發人員或者架構師直接與領域專家(或者說客戶)進行溝通來建模,這些業務模型也是以后開發人員進行設計和實現的依據。

領域模型被當作開發人員之間,開發人員與領域專家之間溝通的橋梁,這樣可以閉免開發人員用錯誤的方式去實現功能。實際上很多優秀的開發人員,都會很自然的將現實世界中的問題進行抽象,然后用計算機的語言表示出來,我們稱之為面向對象。但是由于缺少親臨其境的體驗,往往會離真實的業務模型有一些距離。

我們舉一個例子來說明一下這個問題,假如我們要開發一個電子商務的網站,這個需求已經非常清楚了,現在那么多的電子商務網站直接照抄一個就可以了。現在我們來做一個下單的功能,來看看怎么去實現 。

作為一個高級程序員,我們得用面向對象的方式去開發,先建類。于是我們有了用戶,訂單,訂單項的類,用戶創建訂單然后往訂單里面添加商品,添加訂單項的時候為了方便,我們只需要傳入產品ID和數量就可以了,于是Order類有一個AddItem的方法。

作為一個高級程序員,一看這圖感覺很完美,有木有? 好,下面開始實現AddItem方法。

Order里面是一個OrderItem的集合,而這個AddItem的方法接收的是productId,我去哪里搞個Product對象給你?我不可能在這個實體里面直接去查數據庫吧?本來是沖著這個技術點想咨詢一下大家,后來在小組里面討論了一下,我恍然大悟,上面的實體就是我從代碼的層面去思考想出來的,下單嘛,當然是用戶,訂單和訂單項嘍。可是只要去網上買過東西都知道,用戶是不會直接往訂單里面加東西的,而是先把商品加入購物車,然后再通過“結算”一次性就根據購物車生成了一張訂單,壓根沒有往訂單里面添加訂單項的行為。這才是真正的用戶行為(領域邏輯)所以后來,我們的實體變成這樣了:

所以業務是這樣的:

  • 未注冊用戶也可以將商品添加到購物車中,但是不能下訂單。
  • 并且購物車中的商品不能保存起來,用戶離開這個網站(一般是關掉瀏覽器),購物車中的商品就會消失。
  • 注冊用戶購物車中的商品可以長期永久保存,通過購物車的“結算功能”,將購物車中選中的商品轉化為訂單。
  • 所以購物車,應該在用戶注冊的時候就應該創建好,對應我們上面的User實體中的CreatShoppingCart()方法。下面我們先來簡單實現一下注冊的代碼。

//User領域實體代碼

  1. namespace RepositoryAndEf.Domain  
  2. {  
  3.     public class User : BaseEntity  
  4.     {  
  5.         public string Name { get; set; }  
  6.         public string Email { get; set; }  
  7.         public string Password { get; set; }  
  8.         public Guid ShoppingCartId { get; set; }  
  9.         public virtual ShoppingCart ShoppingCart { get; set; }  
  10.         public virtual ICollection<Order> Orders { get; set; }  
  11.  
  12.         public void CreateShoppingCart()  
  13.         {  
  14.             ShoppingCart = new ShoppingCart  
  15.             {  
  16.                 Id = Guid.NewGuid(),  
  17.                 Customer = this,  
  18.                 CustomerId = Id,  
  19.             };  
  20.  
  21.             ShoppingCartId = ShoppingCart.Id;  
  22.         }  
  23.     }  

//領域層 UserService.cs代碼

  1. namespace RepositoryAndEf.Domain  
  2. {  
  3.     public class UserService  
  4.     {  
  5.         private IRepository<User> _userRepository;  
  6.  
  7.         public UserService(IRepository<User> userRepsoitory)  
  8.         {  
  9.             _userRepository = userRepsoitory;  
  10.         }  
  11.  
  12.         public virtual User Register(string email, string name, string password)  
  13.         {  
  14.             var user = new User  
  15.             {  
  16.                 Id = Guid.NewGuid(),  
  17.                 Email = email,  
  18.                 Name = name,  
  19.                 Password = password  
  20.             };  
  21.  
  22.             user.CreateShoppingCart();  
  23.             _userRepository.Insert(user);  
  24.             return user;  
  25.         }  
  26.     }  

//應用層 UserService.cs代碼

  1. namespace RepositoryAndEf.Service  
  2. {  
  3.     public class UserService : IUserService  
  4.     {  
  5.         protected Domain.UserService DomainuUserService  
  6.         {  
  7.             get  
  8.             {  
  9.                 return EngineContext.Current.Resolve<Domain.UserService>();  
  10.             }  
  11.         }  
  12.  
  13.         public User Register(string email, string name, string password)  
  14.         {  
  15.             var user = DomainuUserService.Register(email, name, password);  
  16.             return user;  
  17.         }  
  18.     }  

上面是我們一次建模的過程,是一個將業務轉變成代碼,將現實世界抽象成軟件世界的過程。我們需要畫出模型不斷的與業務人員(領域專家)去溝通,然后不斷的重構去完善我們的模型,以至于這個模型能最準確的反映真實的業務。這是在最開始的設計階段,是需求溝通階段就需要做的工作,并且會一直貫穿我們后面的開發甚至維護階段,沒有人可以一開始就把領域模型建的100%準確,需求是復雜的,并且需求還是隨時變化的,所以模型也會一直發生改變。它將作為開發人員與業務人員、測試人員以及開發人員自己之間溝通的橋梁。而DDD與其它方法論的區別之處就在于,它還提供了一整套的體系來保證后續對領域模型的重構不會讓系統變得四分五裂,比如架構分層,倉儲,依懶注入等等,我們后面再慢慢探討。

在DDD中,領域模型分為三種:

  1. 實體
  2. 值對象
  3. 領域服務

區分實體、值對象和領域服務

我們不打算去解釋以上的概念,我相信只要你搜索一下就可以得到很全面準確的答案。但是重要的是我們一定要理解3者之間的區別,什么時候是實體,什么時候是值對象,又是什么時候我們該用領域服務呢?我想這是剛接觸DDD的人都難免會有些糾結的地方吧,在這里就強調一下。

實體相對于值對象而言擁有“標識”的概念,標識可以讓我們持續性的跟蹤實體。標識和數據庫里面的“主鍵”是不一樣的概念,主鍵是技術上的概念,但是標識是業務上的概念。

在我們上面的例子中用戶ID是標識,我們用它來持續性的跟蹤我們的用戶。訂單ID是標識,我們用它來持續性的跟蹤訂單,同時我們的用戶和訂單都是有著不同的狀態。但是對于用戶的地址來說,我們用什么來做標識呢?在電子商務網站這樣的業務里面,我們不需要去持續的跟蹤這個地址信息,它在我們的系統里面也不會有著像訂單從“創建”、“已付款”、“已發貨”、“已收貨”等這樣的狀態,所以地址信息的我們系統中就是一個值對象。

但是我們如果換了一個系統,比如說死慢的長城寬帶,他們把地址作為跟蹤對象。同一個地址,誰都可以去注冊,但是同一個時間只允許一個人去注冊,那么這個地址對于長城寬帶來說就去要去持續性的跟蹤,有“開戶”,“銷戶”的狀態。那么地址信息對于長城寬帶來說就是一個實體。

解決完實體和值對象,領域服務就好說了,一些重要的領域操作,既不屬于實體也不屬于值對象,那就可以把它放到服務中了。比如說我們上面的領域服務UserService里面的注冊操作,注冊這個操作可以說就是將這個用戶保存到我們的系統中。在注冊之間,這個用戶是不存在的,我們又怎么能把注冊這個操作放到User實體中去呢?所以把它放到領域服務中成了我們最好的選擇。

即使是這樣,哪些操作應該放到領域服務中對于很多初學者來說還是一件比較難選擇的問題。也許只有慢慢的對業務越來越了解,對DDD應用的越來越熟,我們就會少一點糾結。

#p#

厘清業務主次-聚合與聚合根

在上面的模型中,我們有很多關系的存在:用戶-購物車(1對1),用戶-訂單-訂單項-產品(1對多,1對1),購物車-購物車項-產品等。在DDD中,我們把這樣多個模型用關聯串起來組成一個聚合(aggregation)

在我們的模型中,購物車-購物車項是一個聚合,訂單-訂單項是一個聚合我們通常需要保護這些聚合的一致性,比如說我們把一個訂單刪掉了,那么這個訂單的訂單項也需要一起刪除,否則他們存在也沒有任何的意義。以前我們還會用到觸發器,但是大家都知道這個東西維護起來比較麻煩,寫起來也不方便等,所以后來大家都是在代碼中來控制。但是一直沒有一個好的約束說我們如何去更好的控制這些一致性,代碼一直都很散亂,直到DDD,我們有了聚合和聚合根的概念,“我們通過為每一個聚合選擇一個根,并通過根來控制所有對邊界內的對象的訪問。外部對象只能持有根的引用;由于根控制了訪問,因此我們無法繞過它去修改內部元素。我們后面還會說到只能為根來建立Repository,這也是為了確保我們這里面講的數據的一致性。

在我們上面的聚合中,只能通過購物車實體來操作購物車項,而不能你自己寫一個保存的方法直接就把購物車項給保存到數據庫中去了。這就是聚合和聚合根起到的作用。我們來看一下我們購物車實體的代碼:

  1. namespace RepositoryAndEf.Domain  
  2. {  
  3.     public class ShoppingCart : BaseEntity  
  4.     {  
  5.         public ShoppingCart()  
  6.         {  
  7.             Items = new List<ShoppingCartItem>();  
  8.         }  
  9.  
  10.         #region Properties  
  11.  
  12.         public Guid CustomerId { get; set; }  
  13.         public virtual User Customer { get; set; }  
  14.         public virtual ICollection<ShoppingCartItem> Items { get; set; }  
  15.  
  16.         #endregion  
  17.  
  18.         #region Methods  
  19.         public void AddItem(Product product, int quantity)  
  20.         {  
  21.             // 如果該產品ID已經存在于購物車中,我們直接更改數量即可  
  22.             var repetitiveCartItem = Items.FirstOrDefault(  
  23.                 i => i.ProductId == product.Id);  
  24.  
  25.             if (repetitiveCartItem != null)  
  26.             {  
  27.                 repetitiveCartItem.Quantity += quantity;  
  28.                 return;  
  29.             }  
  30.  
  31.             Items.Add(new ShoppingCartItem  
  32.             {  
  33.                 Product = product,  
  34.                 ProductId = product.Id,  
  35.                 Quantity = quantity,  
  36.             });  
  37.         }  
  38.               
  39.         // 更改購物車數量  
  40.         public void ChangeProductQuantity(Guid productId, int newQuantity)  
  41.         {  
  42.             var items = Items as ICollection<ShoppingCartItem>;  
  43.             var existingCartItem = items.FirstOrDefault(  
  44.                 i => i.ProductId == productId);  
  45.  
  46.             if (existingCartItem == null)  
  47.             {  
  48.                 throw new InvalidOperationException(  
  49.                     "Cannot find the product in shopping cart");  
  50.             }  
  51.             existingCartItem.Quantity = newQuantity;  
  52.         }  
  53.  
  54.         // 從購物車中移除該產品  
  55.         public void RemoveItem(Guid productId)  
  56.         {  
  57.             var items = Items as ICollection<ShoppingCartItem>;  
  58.             var existingCartItem = items.FirstOrDefault(  
  59.                 i => i.ProductId == productId);  
  60.  
  61.             if (existingCartItem == null)  
  62.             {  
  63.                 throw new InvalidOperationException(  
  64.                     "Cannot find the product in shopping cart");  
  65.             }  
  66.  
  67.             items.Remove(existingCartItem);  
  68.         }  
  69.         #endregion  
  70.     }  

大家可以看到我們購物車實體的邏輯很清晰,因為我們很明確購物車擁有哪些操作。當然還有另一種做法即把這些操作都放到用戶實體中去,因為最終其實是用戶做的這些操作。那我們的聚合就變成了用戶-購物車-購物車項,這樣也沒有什么不可以,反而更符合真實的場景。但是會導致我們的聚合過龐大,也就是說我必須要先有用戶實體才能進行操作,用戶用戶可能會綁上很多的東西:購物車、訂單、地址等等。在現在都是ajax來操作的大型網站中,我們需要在服務端把這個用戶請求加載出來再執行添加購物車的操作呢?還是可以直接加載購物車實體來操作呢?這就是一個粒度的問題,不同的問題和場景,大家可以區別來對待。總之聚合是可以根據業務或者一些特定需求來做出調整的。比如說購物車-購物車項-產品,這也是一個聚合,但是由于產品的特殊性,我們可以把產品也作為一個聚合根來單獨進行訪問。

我們來看一下應用層ShoppingCartService的代碼:

  1. public class ShoppingCartService : IShoppingCartService  
  2. {  
  3.     private IRepository<ShoppingCart> _shoppingCartRepository;  
  4.     private IRepository<Product> _productRepository;  
  5.  
  6.     public ShoppingCartService(IRepository<ShoppingCart> shoppingCartRepository,  
  7.         IRepository<Product> productRepository)  
  8.     {  
  9.         _shoppingCartRepository = shoppingCartRepository;  
  10.         _productRepository = productRepository;  
  11.     }  
  12.  
  13.     public ShoppingCart AddToCart(Guid cartId, Guid productId, int quantity)  
  14.     {  
  15.         var cart = _shoppingCartRepository.GetById(cartId);  
  16.         var product = _productRepository.GetById(productId);  
  17.         cart.AddItem(product, quantity);  
  18.  
  19.         _shoppingCartRepository.Update(cart);  
  20.         return cart;  
  21.     }  
  22.  

此應用層代碼一出,大家就會發現,這代碼太簡潔了,有木有?因為所有的邏輯、業務都被放到領域實體那里面去處理了。即使我們業務邏輯改變了,或者我們需要重構了,它們都在領域實體那里面,改那里就好了。接下來的問題是,如何確保安全,正確的一次又一次的對領域實體進行重構呢?畢竟它也是各種關聯,各種依懶呀?您請接著往下看我們的單元測試環節。

#p#

獨立領域業務層 - 高內聚,低耦合,可測試

講到這里,請允許我從網上盜一張圖,當然這張圖早就已經是被引用過無數次了,它就是DDD中使用的分層結構。

關于這個分層,每一層是干什么的,具體怎么玩,大家可以看一下dax的這一篇文章講解的很清楚。總之,我們的領域模型以及相關的類比如工廠等會被獨立成為一層來與應用層和基礎設計層交互。

 領域層是獨立的,首先它是應用層的下層,所以肯定不會有對應用層的依懶,但是領域有一些模型或者服務少不了是要與數據庫打交道的,比如說我們在注冊用戶的時候需要去驗證當前的郵箱是不是已經被占用了。而這一類操作都是屬于基礎設施層做的事情,包含像一些數據庫操作,日志,緩存等等。那么我們如何避免領域層對基礎設施層的依懶呢?感謝面向對象設計 - 面向接口編程,只不過這里面的場景特別有代表性,它是一個非常常見的問題,于是它成為了一個模式:倉儲(Repository)。

  1. namespace RepositoryAndEf.Core.Data  
  2. {  
  3.     public partial interface IRepository<T> where T : BaseEntity  
  4.     {  
  5.         T GetById(object id);  
  6.  
  7.         IEnumerable<T> Get(  
  8.             Expression<Func<T, Boolean>> predicate);  
  9.  
  10.         bool Insert(T entity);  
  11.         bool Update(T entity);  
  12.         bool Delete(T entity);  
  13.     }  

一般情況下,我們會把倉儲的接口放到領域層,或者也可以再建一個Core層來作個項目最下面的那一層提供一些最公共的組件部分。關于倉儲的代碼,大家在上面領域服務UserService中的注冊代碼中就已經見到過了。可能需要注意的是,Repository用來將數據庫與其它的業務和技術分離,所以我們在領域層中使用它,還在應用層中使用它。

Repository讓我們專注于模型,不用去考慮持久化的問題。更為重要的一點是,因為它是接口,所以我們可以很方便的替代它,或者模擬一個實現來對我們的領域模型進行單元測試。下面是我們實現的MockRepository的代碼:

  1. public class MockRepository<T>: IRepository<T> where T : BaseEntity  
  2. {  
  3. private List<T> _list = new List<T>();  
  4.  
  5. public T GetById(Guid id)  
  6. {  
  7.     return _list.FirstOrDefault(e => e.Id == id);  
  8. }  
  9.  
  10. public IEnumerable<T> Get(Expression<Func<T, bool>> predicate)  
  11. {  
  12.     return _list.Where(predicate.Compile());  
  13. }  
  14.  
  15. public bool Insert(T entity)  
  16. {  
  17.     if (GetById(entity.Id) != null)  
  18.     {  
  19.         throw new InvalidCastException("The id has already existed");  
  20.     }  
  21.  
  22.     _list.Add(entity);  
  23.     return true;  
  24. }  
  25.  
  26. public bool Update(T entity)  
  27. {  
  28.     var existingEntity = GetById(entity.Id);  
  29.     if (existingEntity == null)  
  30.     {  
  31.         throw new InvalidCastException("Cannot find the entity.");  
  32.     }  
  33.  
  34.     existingEntity = entity;  
  35.     return true;  
  36. }  
  37.  
  38. public bool Delete(T entity)  
  39. {  
  40.     var existingEntity = GetById(entity.Id);  
  41.     if (existingEntity == null)  
  42.     {  
  43.         throw new InvalidCastException("Cannot find the entity.");  
  44.     }  
  45.  
  46.     _list.Remove(entity);  
  47.     return true;  

下面我們給我們User領域實體的注冊方法加一個檢查Email是否存在的邏輯。

  1. public virtual User Register(string email, string name, string password)  
  2. {  
  3.     if (_userRepository.Get().Any(u => u.Email == email))  
  4.     {  
  5.         throw new ArgumentException("email has already existed");  
  6.     }  
  7.  
  8.     var user = new User  
  9.     {  
  10.         Id = Guid.NewGuid(),  
  11.         Email = email,  
  12.         Name = name,  
  13.         Password = password  
  14.     };  
  15.  
  16.     user.CreateShoppingCart();  
  17.     _userRepository.Insert(user);  
  18.     return user;  

在我們真實的Repository出來之前,不管我們是打算是EF,還是NHibernate,我們現在只要對這個Mock的Repository來編程或者進行單元測試就可以了。

//UserService領域服務在單元測試

  1. public class UserServiceTests  
  2. {  
  3.     private IRepository<User> _userRepository = new MockRepository<User>();  
  4.  
  5.     [Fact]  
  6.     public void RegisterUser_ExpectedParameters_Success()  
  7.     {  
  8.         var userService = new UserService(_userRepository);  
  9.         var registeredUser = userService.Register(  
  10.             "hellojesseliu@outlook.com",   
  11.             "Jesse",   
  12.             "Jesse");  
  13.  
  14.         var userFromRepository = _userRepository.GetById(registeredUser.Id);  
  15.  
  16.         userFromRepository.Should().NotBe(null);  
  17.         userFromRepository.Email.Should().Be("hellojesseliu@outlook.com");  
  18.         userFromRepository.Name.Should().Be("Jesse");  
  19.         userFromRepository.Password.Should().Be("Jesse");  
  20.     }  
  21.  
  22.     [Fact]  
  23.     public void RegisterUser_ExistedEmail_ThrowException()  
  24.     {  
  25.         var userService = new UserService(_userRepository);  
  26.         var registeredUser = userService.Register(  
  27.             "hellojesseliu@outlook.com",   
  28.             "Jesse",   
  29.             "Jesse");  
  30.  
  31.         var userFromRepository = _userRepository.GetById(registeredUser.Id);  
  32.         userFromRepository.Should().NotBe(null);  
  33.  
  34.         Action action = () => userService.Register(  
  35.             "hellojesseliu@outlook.com",   
  36.             "Jesse_01",  
  37.             "Jesse");  
  38.         action.ShouldThrow<ArgumentException>();  
  39.     }  

我們用的XUnit.net作單元測試框架,同時用了Fluent Assertions。

結果很漂亮,有木有?有了單元測試來為我們的領域模型保駕護航,我們就可以安全的進行重構了。

 

干凈漂亮的代碼

經常有人說代碼是一件藝術,碼農都是藝術家。我很喜歡這句話,如果你也認同,那就請像對待藝術品一樣對待我們的代碼,精心的打磨它。并且你不一定要非常的有經驗才可以干這件事情;

如果你剛入行,那至少保證一代碼可讀性好(好的命名,代碼邏輯清晰等);
再往上一點,你要能夠更好的組織代碼(類,函數);
等到你也成為專家了,那就開始考慮一些重用性,可擴展性,可維護性,可測試性的這些比較范的東西了;
而最后就上升到架構層面,考慮系統各個組件之間通訊,分層,等等。最后你就成為碼神了。

DDD里面引入的一些思路包括分層、依懶注入、倉儲等,可以給我們一些指導,大家從上面的代碼也可以看出這些代碼組織的很好,邏輯也不會散亂的到處都是。當然這個項目代碼量有限,說服力是有限的,后面我們還會嘗試去加入應用層的代碼。代碼已經放到CodePlex上去了:http://repositoryandef.codeplex.com
歡迎大家Follow。注意代碼還沒有寫完,只是一個初級版本,我們后面會慢慢完善。這個項目會使用EF來作業ORM框架,Autofac作依懶注入容器,用Xunit作單元測試框架的同時引入了Fluent Assertions。

小結

本文主要介紹了DDD的一些基礎概念:

  • 領域模型:領域實體、領域服務以及值對象;建模一定要從真實的領域業務出發,多與領域專家進行溝通來完善模型。
  • 聚合與聚合根:它的主要作用是用來確保各種關系下的實體的數據一致性;但是確認聚合根這個過程,實際上也是對業務的梳理過程。
  • 架構分層: 每一層都職責清楚;依懶于接口來降低耦合。
  • 封裝和測試: 所有的業務都放到領域層,同時對領域層進行單元測試來確保最核心的邏輯不會遭到破壞。

個人感覺沒有必要太強調Repository的概念,從領域實體的生命周期(創建-持久化到數據庫-銷毀-從數據庫重建)你會發現其實這個過程很普遍,并不是只有DDD才有的。所以我認為Repository主要是將數據訪問功能給隔離開,避免領域實體對基礎設施層的依懶。那它和三層有什么區別? BLL 引用DAL不也是依懶于接口么?給我的感覺是,DDD的領域實體持久化這一塊就是三層里面的思路。這可能是在學習DDD初期的想法,因為真實的大型項目中是不會直接把領域實體給持久化的,那個叫DTO,于是Repository<>里面放的就不是我們的領域實體了,而是將領域實體轉換成對應的DTO。 

是否一定要使用DTO呢?領域實體和DTO互相轉換,最后到了表現層DTO還要和ViewModel轉換,會不會帶來復雜性和性能上的損失?Repository和EF還有Unit Of Work怎么來協調?抱怨寫單元測試么?怎么樣讓寫單元測試不變成只是走過場而已? 這些問題留給我們后面再解決吧。

本文出自自:http://www.cnblogs.com/jesse2013/p/the-first-glance-of-ddd.html

責任編輯:林師授 來源: Jesse Liu的博客
相關推薦

2023-07-17 18:39:27

業務系統架構

2020-09-27 14:24:58

if-else cod業務

2025-11-05 01:00:00

架構業務系統MVC

2019-01-02 05:55:30

領域驅動軟件復雜度

2022-07-04 19:02:06

系統業務思考

2022-04-07 17:30:31

Flutter攜程火車票渲染

2014-09-26 10:00:25

驅動設計DDD領域

2014-04-10 09:49:38

System z關鍵業務

2024-12-20 19:38:01

ToB業務狀態轉換

2023-05-30 07:56:23

代碼軟件開發

2021-09-08 09:22:23

領域驅動設計

2017-04-21 07:41:37

iOS自動化測試容器

2023-02-24 18:47:37

供應鏈實時數倉

2025-02-07 08:16:26

Java開發者代碼

2019-08-14 08:52:40

業務代碼運營

2013-04-08 13:50:19

.NET系統架構設計DDD

2018-12-11 14:18:11

領域驅動設計ThoughtWork

2023-08-29 07:53:17

領域驅動設計

2017-06-05 15:08:14

容量全鏈路流量

2013-04-11 09:52:17

.NET設計模式TDD
點贊
收藏

51CTO技術棧公眾號

精品无码久久久久久久| 久久精品99久久| 四虎地址8848| 国产成人精品亚洲线观看| 亚洲成a人在线观看| 国产自产在线视频一区| 神马久久久久久久 | 91av精品| 亚洲精品美女视频| 一区二区三区 欧美| 亚洲性图自拍| 91小视频免费观看| 91九色国产视频| 特黄视频免费看| 国产香蕉在线| 国内精品伊人久久久久影院对白| 久久久久久有精品国产| 中字幕一区二区三区乱码| 九色精品蝌蚪| 欧美在线观看18| 欧美成人精品免费| 欧美私人网站| 久久夜色精品国产噜噜av| 91色在线视频| 看黄色一级大片| 亚洲性感美女99在线| 中文字幕av日韩| 国产毛片毛片毛片毛片毛片毛片| 亚洲人体在线| 在线免费视频一区二区| 成人性免费视频| 91麻豆免费在线视频| 欧美国产一区二区在线观看| 国内一区二区在线视频观看| 国产露脸无套对白在线播放| 日韩成人精品视频| 国产91对白在线播放| 久久精品性爱视频| 中文字幕一区二区三三| 日韩天堂在线视频| 91在线无精精品白丝| 日韩三级视频| 亚洲аv电影天堂网| 伊人国产精品视频| 国产亚洲人成a在线v网站| 欧美性色视频在线| 成人午夜视频在线观看免费| 五月天激情在线| 亚洲激情中文1区| 日本福利视频导航| 老司机福利在线视频| 国产精品久久毛片| 制服国产精品| 国产在线看片| 又紧又大又爽精品一区二区| 在线成人性视频| 免费黄色网页在线观看| 亚洲欧洲av另类| 中文字幕久久综合| 国产原厂视频在线观看| 亚洲免费电影在线| 免费看日本黄色| 黄色的视频在线观看| 伊人一区二区三区| 青青视频免费在线观看| 怡红院av在线| 午夜视频在线观看一区| 国产99久久九九精品无码| 亚洲欧美韩国| 欧美在线观看视频在线| 国产欧美激情视频| 亚洲区欧洲区| 亚洲最大色网站| 欧美一级视频在线播放| 人人草在线视频| 欧美性猛交xxx| www.日本xxxx| 国产精品日本一区二区三区在线 | 国产欧美中文在线| 亚洲视频小说| 色呦呦在线免费观看| 午夜视频在线观看一区二区| 久久久久久久久久久久久国产精品 | 国产三级电影在线观看| 久久久99精品久久| 亚洲欧洲日韩综合二区| 国产黄大片在线观看画质优化| 亚洲男同性恋视频| a级黄色一级片| 四虎4545www精品视频| 欧美日韩激情一区二区三区| 国产资源中文字幕| 群体交乱之放荡娇妻一区二区| 亚洲夜晚福利在线观看| 欧美一区免费观看| 亚洲免费播放| 国产精品丝袜久久久久久不卡| 精品毛片在线观看| 久久久久一区二区三区四区| 亚洲欧美日韩国产yyy| 欧美男男video| 一本久久a久久免费精品不卡| 日产精品一线二线三线芒果| 免费黄色电影在线观看| 精品免费在线观看| 在线看免费毛片| 欧美深夜视频| 久久高清视频免费| 日韩综合在线观看| 国产成人精品综合在线观看 | 亚洲三级在线免费| 国产原创popny丨九色| 国产麻豆一区| 日韩www在线| 波多野结衣家庭教师| 亚洲综合欧美| 99久久精品免费看国产四区| av在线1区2区| 欧美色videos| 国产口爆吞精一区二区| 国产精品456| 久久狠狠久久综合桃花| 欧美极品影院| 亚洲午夜在线观看视频在线| 可以在线看的黄色网址| 深夜福利一区| 成人福利电影精品一区二区在线观看 | 99国产视频在线| 福利片在线观看| 天天操天天干天天综合网| 五月天婷婷影视| 神马电影久久| 91高清免费视频| 国产熟女一区二区丰满| 国产精品网站导航| 久久久久国产精品熟女影院| 精品嫩草影院| 欧美精品videossex88| 国产乱码久久久| 国产精品欧美一区喷水| 国产精品wwwww| 老司机aⅴ在线精品导航| 欧美激情一区二区三区高清视频| 国产美女三级无套内谢| 日韩码欧中文字| 在线观看免费视频高清游戏推荐 | 亚洲人一区二区| 欧美一级鲁丝片| 亚洲精品成a人在线观看| 91热这里只有精品| 欧美人妖视频| 久久男人av资源网站| 国产手机av在线| 亚洲精品免费视频| 无码国产精品一区二区高潮| 伊人情人综合网| 亚洲在线视频福利| a在线免费观看| 欧美成人r级一区二区三区| 妺妺窝人体色www聚色窝仙踪| 伊人久久大香线蕉综合热线 | heyzo亚洲| 福利电影一区 | 亚洲美女尤物影院| 日韩经典第一页| 亚洲欧美日韩激情| 久久久久亚洲蜜桃| 亚洲欧美在线精品| 亚洲精品极品少妇16p| 亚洲最大福利视频网| 污的网站在线观看| 亚洲国产小视频| 51国产偷自视频区视频| 久久精品视频免费观看| 九色91popny| 在线看片不卡| 精品久久蜜桃| 99久久婷婷国产综合精品首页| 精品国产一区二区三区久久狼黑人 | 国产在线一区二区三区| 午夜dj在线观看高清视频完整版| 亚洲成人av中文字幕| 中文字幕日韩免费| 亚洲美女视频一区| 国产精品久久无码| 奇米一区二区三区| 欧美做暖暖视频| 欧美人成在线观看ccc36| 国产精品久久久久久久久久久久久| 欧美jizz18hd性欧美| 精品国产成人系列| 久草热在线观看| 亚洲一区二区在线观看视频| 97人妻精品一区二区免费| 久久99久久99| 秋霞无码一区二区| 成人动漫免费在线观看| 91偷拍精品一区二区三区| 中文字幕高清在线播放| 中文字幕日韩在线观看| 国产 日韩 欧美 精品| 美女免费视频一区| 91产国在线观看动作片喷水| 青青草娱乐在线| 欧美日韩国产美| 久久久久99精品| 亚洲国产精品传媒在线观看| 四川一级毛毛片| 一本综合精品| 91久久精品国产91性色tv| 亚洲一区二区观看| 小嫩嫩12欧美| 成人高清免费观看| 国产不卡一区二区在线观看| 日韩电影免费观| 色综合久综合久久综合久鬼88| 欧美另类自拍| 亚洲第一av在线| 亚洲性生活大片| 欧美视频中文字幕在线| 亚洲国产123| 国产亚洲成aⅴ人片在线观看| 老熟女高潮一区二区三区| 日本不卡在线视频| 欧美亚洲另类色图| 亚洲精品1区| 日本在线视频www色| 欧美精选视频在线观看| 久久视频在线观看中文字幕| 中文字幕av一区二区三区四区| 国产剧情日韩欧美| 久久99久久99精品免观看软件| 欧美极品少妇与黑人| a视频在线免费看| 日韩在线观看免费全| 国产精品四虎| 亚洲性日韩精品一区二区| 青青草免费观看免费视频在线| 亚洲精品一区二区三区在线观看| 国产aⅴ一区二区三区| 欧美片网站yy| 在线视频 91| 在线观看精品一区| 波多野结衣电影在线播放| 欧美日韩免费看| 午夜影院在线看| 午夜国产不卡在线观看视频| 国产在线成人精品午夜| 一区二区三区免费观看| 久久久久久久久艹| 一区二区三区四区亚洲| 视频这里只有精品| 伊人开心综合网| 日本熟妇色xxxxx日本免费看| 亚洲夂夂婷婷色拍ww47| www.av视频在线观看| 亚洲国产精品麻豆| 粉嫩aⅴ一区二区三区| 亚洲成人激情综合网| 日韩免费在线视频观看| 舔着乳尖日韩一区| 中文字幕在线看人| 欧美在线短视频| 国产精品色综合| 91精品国产综合久久蜜臀| 国产熟女一区二区丰满| 日韩欧美不卡在线观看视频| 成人乱码一区二区三区| 亚洲电影免费观看高清| 视频一区二区三区在线看免费看| 亚洲免费精彩视频| 亚洲无吗一区二区三区| 日韩欧美一区二区三区免费看| 樱花www成人免费视频| 欧美 日韩 国产一区二区在线视频| 人妻无码一区二区三区四区| 99亚洲伊人久久精品影院红桃| 久久免费视频3| 麻豆精品一区二区综合av| 原创真实夫妻啪啪av| 99久久精品99国产精品| 天天干天天舔天天操| 亚洲精选免费视频| 日韩av在线播放观看| 91黄色免费观看| 国产精品伦理一区| 欧美精品一区二区精品网| 国内av一区二区三区| www.色综合| 僵尸再翻生在线观看| 国产精品视频不卡| 中文在线综合| 欧美一区三区二区在线观看| 91视频久久| 色综合久久久久无码专区| 免费精品视频在线| 亚洲啪av永久无码精品放毛片 | 色爱区综合激月婷婷| 一本一道精品欧美中文字幕| 亚洲第一网站男人都懂| 欧美成人三区| 97不卡在线视频| 欧美jizz18| 黄色91av| 欧美在线高清| 别急慢慢来1978如如2| 国产成人精品一区二区三区四区| 色一情一交一乱一区二区三区| 一区二区三区中文免费| 天天操天天干天天摸| 精品国产一区二区亚洲人成毛片| 一本一道波多野毛片中文在线| 97久久久久久| 国产精品亚洲欧美一级在线 | 黄网在线观看| 欧美寡妇偷汉性猛交| 亚洲精品一区三区三区在线观看| 国产乱子伦精品| 91精品秘密在线观看| 欧美在线观看视频网站| 成人高清视频免费观看| 波多野结衣亚洲一区二区| 色综合久久久久网| 日本高清视频免费观看| 欧美成人午夜激情| 久久青草视频| 午夜精品美女久久久久av福利| 99在线精品视频在线观看| 91精品国产高清91久久久久久| 国产精品国产a| www.亚洲激情| 亚洲色图狂野欧美| 蜜桃视频在线观看免费视频| 97se视频在线观看| 国产主播在线观看| 亚洲电影在线播放| 成人av一区二区三区在线观看| 最新69国产成人精品视频免费| 成人在线视频播放| 欧美日韩精品久久久免费观看| 亚洲无线视频| 污污污www精品国产网站| 一区二区三区中文在线观看| 99久久国产免费| 九九热最新视频//这里只有精品 | 91日韩在线视频| 99久久精品国产亚洲精品| www.日本一区| 国产精品伦一区二区三级视频| 波多野结衣一区二区三区在线| 亚洲精品中文字幕av| 亚洲国产福利| 欧美成人第一区| 久久久久久网| 欧美熟妇激情一区二区三区| 欧美自拍丝袜亚洲| 91在线高清| 成人午夜一级二级三级| 亚洲精品国产偷自在线观看| 欧美国产在线一区| 亚洲一区二区三区四区五区中文| 亚洲AV无码一区二区三区少妇| 欧美日韩高清区| 久久久免费毛片| 一本大道熟女人妻中文字幕在线| 95精品视频在线| 色一情一乱一伦| 在线视频欧美日韩精品| 日本一区二区三区中文字幕| 中文字幕中文字幕一区三区| 国产精品自在在线| 日韩高清免费av| 亚洲欧美日韩精品久久| 久久av影院| 日本免费成人网| 91在线码无精品| 在线免费观看视频网站| 欧美老女人xx| 日韩美女国产精品| 911福利视频| 亚洲一区二区三区精品在线| 天天干在线观看| 国产精品视频在线观看| 欧美黄色一区| 欧美特黄一区二区三区| 7878成人国产在线观看| h片精品在线观看| 日韩一本精品| 国产成都精品91一区二区三| 成人免费毛片男人用品| 美女久久久久久久久久久| 日本欧美韩国国产| 亚欧激情乱码久久久久久久久| 亚洲激情六月丁香| 国产一级片在线播放| 91天堂在线视频| 亚洲欧美久久久| 性色av无码久久一区二区三区| 日韩电影免费观看在线观看| 亚洲成a人片777777久久|