当前位置: 首页 > 编程学习 > 软件工程 > 领域驱动 > 正文

用DDD设计一个电商网站(七) 实现售价上下文

2018-04-22 来源:博客园/Zachary_Fan

一、前言

上一篇我们已经确立的购买上下文和销售上下文的交互方式,传送门在此: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);
        }
    }