閱讀目錄 前言 如何在一個項目中實現多個上下文的業務 售價上下文與購買上下文的集成 結語 一、前言 前幾篇已經實現了一個最簡單的購買過程,這次開始往這個過程中增加一些東西。比如促銷、會員價等,在我們的第一篇文章(如何一步一步用DDD設計一個電商網站(一)—— 先理解核心概念)中規劃的上下文映射圖可以 ...
閱讀目錄
一、前言
前幾篇已經實現了一個最簡單的購買過程,這次開始往這個過程中增加一些東西。比如促銷、會員價等,在我們的第一篇文章(如何一步一步用DDD設計一個電商網站(一)—— 先理解核心概念)中規劃的上下文映射圖可以看到,這些都屬於一個獨立的上下文(售價上下文)。
二、如何在一個項目中實現多個上下文的業務
一般情況下,為了更好的分而治之,把不同的上下文作為單獨的service,然後通過rpc框架(如WCF)來對其訪問是個比較常見的做法。但是在一些小型團隊中,雖然劃分出了不同上下文,但是我們的開發團隊還是同一個。在這種情況下,我個人一般的做法是直接在同一個解決方案中建立不同的項目去做,但是這裡需要在解決方案中明確的劃分好不同上下文之間的邊界,通過代碼審核等手段管理好這個邊界不被破壞。
【圖1】
增加的幾個項目如圖1所示。
三、售價上下文與購買上下文的集成
根據我們第一篇如何一步一步用DDD設計一個電商網站(一)—— 先理解核心概念所定義的上下文映射圖和9種集成模式可以看出,這2個上下文在同一個子域中,並且在我們實際業務場景中,這2者又是相輔相成,所以售價上下文和購買上下文是一種合作關係。確立這個關係之後,那麼這個促銷的計算邏輯到底是放到哪個上下文種做更合適呢?我們先整理一下幾種可能的方式:
1.購買上下文把購物車中的商品信息丟給售價上下文 --> 售價上下文進行計算 --> 把結果再返回給購買上下文。
2.購買上下文從銷價上下文獲取相關會員價和促銷信息 --> 再本地的購物車對象基礎上進行運算,並直接可運用結果。
3.再抽出一個專門的計算服務(隸屬於售價上下文),去做這個計算的動作。購買上下文把購物車中的商品信息丟給計算服務 --> 計算上下文從銷價上下文獲取到相關會員價和促銷信息 --> 計算 --> 返回結果給購買上下文
我相信1和2是比較主流的2個方式。但是方式2是把售價上下文僅作為一種數據的提供方,這就把合作關係變成了一個上下游的關係,並且這種方式使得促銷規則和購物車強耦合到了一起,不利於促銷規則的變化。在這裡售價上下文只起了一個簡單的數據維護作用,無法完全控制“售價”的定義,沒有很好的做到職責分離。方式1和3對購買上下文來說其實是沒有區別的,只是方式3讓整個數據交互的鏈路多了一層,會產生額外的開銷,好處是服務的粒度更細了,需要結合實際情況權衡一下得失。這裡我選擇1方式來實現,因為我們在項目初期,還是儘可能的減少非業務目的的拆分導致的額外成本。
好了,確定了集成方式之後,先把2個上下文之間用於數據交互的DTO模型定一下,如下圖2(售價上下文的DTO模型),圖3(購買上下文中與前者對應的值對象)。
【圖2】
【圖3】
另外在圖3中可以發現增加了一個ISellingPriceService,抽象了與售價上下文的交互。那麼我們在Mall.Infrastructure.Translators項目中增加對這個上下文的防腐層處理,老3樣SellingPriceAdapter(發起上下文數據請求的適配器)、SellingPriceService(實現ISellingPriceService)、SellingPriceTranslator(把遠程數據對象轉換成本地的值對象),代碼很簡單大家可以在源碼中查看。需要註意的是,這裡的Mall.Infrastructure.Translators項目僅增加了對Mall.Application.SellingPrice項目的引用,類似於把它當作一個遠程資源來對待(按上面所說,如果實際由不同的團隊負責可以物理上的分離到2個解決方案中)。
最後創建一個CartService,裡面的GetCart()方法——獲取購物車信息,來作為調用發起方。這其中的實現使用了最簡單的方式,本地不做任何的數據冗餘,代碼如下:
public class CartService { private readonly static ConfirmUserCartExistedDomainService _confirmUserCartExistedDomainService = new ConfirmUserCartExistedDomainService(); public CartDTO GetCart(string userId) { var cart = _confirmUserCartExistedDomainService.GetUserCart(userId); if (cart.IsEmpty()) { return null; } var sellingPriceCart = DomainRegistry.SellingPriceService().Calculate(cart); return ConvertToCart(cart, sellingPriceCart); } private CartDTO ConvertToCart(Cart cart, SellingPriceCart sellingPriceCart) { return new CartDTO { CartItemGroups = sellingPriceCart.CalculatedFullGroups.Select(ent => new CartItemGroupDTO { CartItems = ent.CalculatedCartItems.Select(e => ConvertToCartItem(e, cart.GetCartItem(e.ProductId))).ToArray(), ReducePrice = ent.ReducePrice }).ToArray(), CartItems = sellingPriceCart.CalculatedCartItems.Select(ent => ConvertToCartItem(ent, cart.GetCartItem(ent.ProductId))).ToArray() }; } private CartItemDTO ConvertToCartItem(SellingPriceCartItem sellingPriceCartItem, CartItem cartItem) { var product = DomainRegistry.ProductService().GetProduct(cartItem.ProductId); return new CartItemDTO { ProductId = cartItem.ProductId, ProductName = product == null ? "商品已失效" : product.SaleName, ReducePrice = sellingPriceCartItem.ReducePrice, SalePrice = cartItem.Price }; } }
四、結語
這次有個全局改動這裡提一下,我在本次編碼中把之前所有的Guid標識全部改為了string類型,弱化了對唯一標識的數據類型約束,提高可擴展性(如自增欄位、其它自定義的唯一標識等),另外還把購物項中的Price改為了UnitPrice,讓語義更加清晰。本篇內容比較粗,歡迎大家探討。
本文的源碼地址:https://github.com/ZacharyFan/DDDDemo/tree/Demo6。
作者:Zachary_Fan
出處:http://www.cnblogs.com/Zachary-Fan/p/6087752.html