如何一步一步用DDD設計一個電商網站(六)—— 給購物車加點料,集成售價上下文


 

 

本系列所有文章

如何一步一步用DDD設計一個電商網站(一)—— 先理解核心概念

如何一步一步用DDD設計一個電商網站(二)—— 項目架構

如何一步一步用DDD設計一個電商網站(三)—— 初涉核心域

如何一步一步用DDD設計一個電商網站(四)—— 把商品賣給用戶

如何一步一步用DDD設計一個電商網站(五)—— 停下腳步,重新出發

如何一步一步用DDD設計一個電商網站(六)—— 給購物車加點料,集成售價上下文

如何一步一步用DDD設計一個電商網站(七)—— 實現售價上下文

如何一步一步用DDD設計一個電商網站(八)—— 會員價的集成

如何一步一步用DDD設計一個電商網站(九)—— 小心陷入值對象持久化的坑

如何一步一步用DDD設計一個電商網站(十)—— 一個完整的購物車

如何一步一步用DDD設計一個電商網站(十一)—— 最后的准備

如何一步一步用DDD設計一個電商網站(十二)—— 提交並生成訂單

如何一步一步用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
出處:https://zacharyfan.com/archives/147.html

 

 

▶關於作者:張帆(Zachary,個人微信號:Zachary-ZF)。堅持用心打磨每一篇高質量原創。歡迎掃描右側的二維碼~。

定期發表原創內容:架構設計丨分布式系統丨產品丨運營丨一些思考。

 

如果你是初級程序員,想提升但不知道如何下手。又或者做程序員多年,陷入了一些瓶頸想拓寬一下視野。歡迎關注我的公眾號「跨界架構師」,回復「技術」,送你一份我長期收集和整理的思維導圖。

如果你是運營,面對不斷變化的市場束手無策。又或者想了解主流的運營策略,以豐富自己的“倉庫”。歡迎關注我的公眾號「跨界架構師」,回復「運營」,送你一份我長期收集和整理的思維導圖。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM