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

来源:http://www.cnblogs.com/Zachary-Fan/archive/2016/12/05/DDD_7.html
-Advertisement-
Play Games

閱讀目錄 前言 明確業務細節 建模 實現 結語 一、前言 上一篇我們已經確立的購買上下文和銷售上下文的交互方式,傳送門在此:http://www.cnblogs.com/Zachary-Fan/p/DDD_6.html,本篇我們來實現售價上下文的具體細節。 二、明確業務細節 電商市場越來越成熟,競爭 ...


閱讀目錄

 

一、前言

  上一篇我們已經確立的購買上下文和銷售上下文的交互方式,傳送門在此:http://www.cnblogs.com/Zachary-Fan/p/DDD_6.html,本篇我們來實現售價上下文的具體細節。

 

二、明確業務細節

  電商市場越來越成熟,競爭也越來越激烈,影響客戶流量的關鍵因素之一就是價格,運營的主要打法之一也是價格,所以是商品價格是一個在電商中很重要的一環。正因為如此也讓促銷演變的越來越複雜,那麼如何在編碼上花點心思來儘可能的降低業務的複雜化帶來的影響和提高可擴展性來擁抱變化就變得很重要了。先從最簡單的開始,我瀏覽了某東的促銷,先把影響價格相關的幾個促銷找出來,暫時得出以下幾個結論(這裡又要提一下,我們實際工作中應在開始編碼之前要做的就是和領域專家討論促銷的細節):

  1.滿減:可以多個商品共同參與,彙總金額達到某個閾值之後減免XX金額。

  2.多買優惠(方式1):可以多個商品共同參與,彙總購買數量達到一定數量得到X折的優惠。

  3.多買優惠(方式2):可以多個商品共同參與,彙總購買數量達到一定數量減免最便宜的X件商品。

  4.限時折扣:直接商品的購買金額被修改到指定值。

  5.滿減促銷的金額滿足點以優惠後價格為準,比如該商品既有限時折扣又有滿減,則使用限時折扣的價格來計算金額滿足點。

  6.優惠券是在之上的規則計算之後得出的金額基礎下計算金額滿足點。

  7.每一個商品的滿減+多買優惠僅能參與一種。並且相同促銷商品在購物車中商品展示的方式是在一組中。

   

三、建模

  根據上面的業務描述先找到其中的幾個領域對象,然後在做一些適當的抽象,得出下麵的UML圖(點擊圖片可查看大圖):

 

                             【圖1】

四、實現

  建模完之後下麵的事情就容易了,先梳理一下我們的業務處理順序:

  1.根據購買上下文傳入的購物車信息獲取產品的相關促銷。

  2.先處理單品促銷。

  3.最後處理多商品共同參與的促銷。

  梳理的過程中發現,為了能夠實現滿減和多買優惠促銷僅能參與一個,所以需要再購買上下文和售價上下文之間傳遞購物項時增加一個參數選擇的促銷唯一標識(SelectedMultiProductsPromotionId)。

  隨後根據上面業務處理順序,發現整個處理的鏈路比較長,那麼這裡我決定定義一個值對象來承載整個處理的過程。如下:

    public class BoughtProduct
    {
        private readonly List<PromotionRule> _promotionRules = new List<PromotionRule>();

        public string ProductId { get; private set; }

        public int Quantity { get; private set; }

        public decimal UnitPrice { get; private set; }

        public decimal ReducePrice { get; private set; }

        /// <summary>
        /// 商品在單品優惠後的單價,如果沒有優惠則為正常購買的單價
        /// </summary>
        public decimal DiscountedUnitPrice
        {
            get { return UnitPrice - ReducePrice; }
        }

        public decimal TotalDiscountedPrice
        {
            get { return DiscountedUnitPrice * Quantity; }
        }

        public ReadOnlyCollection<ISingleProductPromotion> InSingleProductPromotionRules
        {
            get { return _promotionRules.OfType<ISingleProductPromotion>().ToList().AsReadOnly(); }
        }

        public IMultiProductsPromotion InMultiProductPromotionRule { get; private set; }

        public BoughtProduct(string productId, int quantity, decimal unitPrice, decimal reducePrice, IEnumerable<PromotionRule> promotionRules, string selectedMultiProdcutsPromotionId)
        {
            if (string.IsNullOrWhiteSpace(productId))
                throw new ArgumentException("productId不能為null或者空字元串", "productId");

            if (quantity <= 0)
                throw new ArgumentException("quantity不能小於等於0", "quantity");

            if (unitPrice < 0)
                throw new ArgumentException("unitPrice不能小於0", "unitPrice");

            if (reducePrice < 0)
                throw new ArgumentException("reducePrice不能小於0", "reducePrice");

            this.ProductId = productId;
            this.Quantity = quantity;
            this.UnitPrice = unitPrice;
            this.ReducePrice = reducePrice;

            if (promotionRules != null)
            {
                this._promotionRules.AddRange(promotionRules);
                var multiProductsPromotions = this._promotionRules.OfType<IMultiProductsPromotion>().ToList();
                if (multiProductsPromotions.Count > 0)
                {
                    var selectedMultiProductsPromotionRule = multiProductsPromotions.SingleOrDefault(ent => ((PromotionRule)ent).PromotoinId == selectedMultiProdcutsPromotionId);

                    InMultiProductPromotionRule = selectedMultiProductsPromotionRule ?? multiProductsPromotions.First();
                }
            }
        }

        public BoughtProduct ChangeReducePrice(decimal reducePrice)
        {
            if (reducePrice < 0)
                throw new ArgumentException("result.ReducePrice不能小於0");

            var selectedMultiProdcutsPromotionId = this.InMultiProductPromotionRule == null
                ? null
                : ((PromotionRule) this.InMultiProductPromotionRule).PromotoinId;
            return new BoughtProduct(this.ProductId, this.Quantity, this.UnitPrice, reducePrice, this._promotionRules, selectedMultiProdcutsPromotionId);
        }
    }

  需要註意一下,值對象的不可變性,所以這裡的ChangeReducePrice方法返回的是一個新的BoughtProduct對象。另外這次我們的例子比較簡單,單品促銷只有1種。理論上單品促銷是支持疊加參與的,所以這裡的單品促銷設計了一個集合來存放。

  下麵的代碼是處理單品促銷的代碼:

            foreach (var promotionRule in singleProductPromotionRules)
            {
                var tempReducePrice = ((PromotionRuleLimitTimeDiscount)promotionRule).CalculateReducePrice(productId, unitPrice, DateTime.Now);  //在創建的時候約束促銷的重覆性。此處邏輯上允許重覆
                if (unitPrice - reducePrice <= tempReducePrice)
                {
                    reducePrice = unitPrice;
                }
                else
                {
                    reducePrice += tempReducePrice;
                }
            }

  這裡也可以考慮把它重構成一個領域服務來合併同一個商品多個單品促銷計算結果。

  整個應用服務的代碼如下:

    public class CalculateSalePriceService : ICalculateSalePriceService
    {
        private static readonly MergeSingleProductPromotionForOneProductDomainService _mergeSingleProductPromotionForOneProductDomainService = new MergeSingleProductPromotionForOneProductDomainService();

        public CalculatedCartDTO Calculate(CartRequest cart)
        {
            List<BoughtProduct> boughtProducts = new List<BoughtProduct>();

            foreach (var cartItemRequest in cart.CartItems)
            {
                var promotionRules = DomainRegistry.PromotionRepository().GetListByContainsProductId(cartItemRequest.ProductId);
                var boughtProduct = new BoughtProduct(cartItemRequest.ProductId, cartItemRequest.Quantity, cartItemRequest.UnitPrice, 0, promotionRules, cartItemRequest.SelectedMultiProductsPromotionId);
                boughtProducts.Add(boughtProduct);
            }

            #region 處理單品促銷
            foreach (var boughtProduct in boughtProducts.ToList())
            {
                var calculateResult = _mergeSingleProductPromotionForOneProductDomainService.Merge(boughtProduct.ProductId, boughtProduct.DiscountedUnitPrice, boughtProduct.InSingleProductPromotionRules);

                var newBoughtProduct = boughtProduct.ChangeReducePrice(calculateResult);

                boughtProducts.Remove(boughtProduct);
                boughtProducts.Add(newBoughtProduct);
            }
            #endregion

            #region 處理多商品促銷&構造DTO模型
            List<CalculatedFullGroupDTO> fullGroupDtos = new List<CalculatedFullGroupDTO>();
            foreach (var groupedPromotoinId in boughtProducts.Where(ent => ent.InMultiProductPromotionRule != null).GroupBy(ent => ((PromotionRule)ent.InMultiProductPromotionRule).PromotoinId))
            {
                var multiProdcutsReducePricePromotion = (IMultiProdcutsReducePricePromotion)groupedPromotoinId.First().InMultiProductPromotionRule;  //暫時只有減金額的多商品促銷
                var products = groupedPromotoinId.ToList();

                if (multiProdcutsReducePricePromotion == null) 
                    continue;

                var reducePrice = multiProdcutsReducePricePromotion.CalculateReducePrice(products);
                fullGroupDtos.Add(new CalculatedFullGroupDTO
                {
                    CalculatedCartItems = products.Select(ent => ent.ToDTO()).ToArray(),
                    ReducePrice = reducePrice,
                    MultiProductsPromotionId = groupedPromotoinId.Key
                });
            }
            #endregion

            return new CalculatedCartDTO
            {
                CalculatedCartItems = boughtProducts.Where(ent => fullGroupDtos.SelectMany(e => e.CalculatedCartItems).All(e => e.ProductId != ent.ProductId))
                                                    .Select(ent => ent.ToDTO()).ToArray(),
                CalculatedFullGroups = fullGroupDtos.ToArray(),
                CartId = cart.CartId
            };
        }
    }

 

五、結語

   這裡的設計沒有考慮促銷規則的衝突問題,如果做的話把它放在創建促銷規則的時候進行約束即可。

 

 

 

本文的源碼地址:https://github.com/ZacharyFan/DDDDemo/tree/Demo7

 

 

作者:Zachary_Fan
出處:http://www.cnblogs.com/Zachary-Fan/p/DDD_7.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.支付準備 2.支付方法 3.回饋方法 當支付成功時,易寶會訪問這裡用兩種方法訪問:1. 引導用戶的瀏覽器重定向(如果用戶關閉了瀏覽器,就不能訪問這裡了)2. 易寶的伺服器會使用點對點通訊的方法訪問這個方法。(必須回饋success,不然易寶伺服器會一直調用這個方法) 4.pay.jsp paym ...
  • 3.查詢訂單詳細信息 OrderServlet desc.jsp 4.取消訂單、確認收貨 ...
  • 在構造器中可以調用本類的其他重載構造器,不能使用構造器名稱來調用另一個構造器,而是應該使用Java特定的this(….)來調用。 this(….)方法必須出現在構造器中的第一行,用來調用其他重載構造器。調用時參數必須嚴格匹配。 這種調用方式的優點在於一個構造器可以不必重覆編寫其他構造器中已有的代碼, ...
  • 1. 非註解方式 1.1 處理器適配器 上一節中使用的處理器適配器是:org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter。即: SimpleControllerHandlerAdapter適配器能執行實現了Contro ...
  • Python來做應用題及思路 最近找工作頭疼沒事就開始琢磨python解應用題應該可以,順便還可以整理下思路當然下麵的解法只是個人理解,也歡迎大佬們給意見或者指點更好的解決辦法等於優化代碼了嘛,也歡迎大家出點小題目做也可以,如果可以我也會定期專門來做應用題(你弟弟或者你表弟或者外甥等來問應用題在也不 ...
  • 英文文檔: __import__(name, globals=None, locals=None, fromlist=(), level=0) This function is invoked by the import statement. It can be replaced (by impor ...
  • 閑來無事,寫了一段通過類模板實現棧的代碼,分享給大家..... (關於棧的更多的詳細信息,詳見:http://www.cplusplus.com/reference/stack/stack/?kw=stack) 棧的聲明及實現 測試代碼 總結: 以上代碼,僅供個人娛樂. 在STL中的棧(stack) ...
  • 我遇到的問題 在做上位機軟體的時候,需要將上位軟體的命令傳輸到每個被控席位,也需要和被控電腦進行數據交換,我們的被控端是伺服器,也可能是客戶端,甚至有時候會遇到客戶端先啟動服務端後啟動情況,要控制的機器也可能是多台,同時我們要支持TCP和UDP兩種協議。 好酒加冰塊-交互過程 如果採用Tcp作為連 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...