博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
如何一步一步用DDD设计一个电商网站(七)—— 实现售价上下文
阅读量:6969 次
发布时间:2019-06-27

本文共 7669 字,大约阅读时间需要 25 分钟。

本系列所有文章

 

 

阅读目录

 

一、前言

  上一篇我们已经确立的购买上下文和销售上下文的交互方式,传送门在此:,本篇我们来实现售价上下文的具体细节。

 

二、明确业务细节

  电商市场越来越成熟,竞争也越来越激烈,影响客户流量的关键因素之一就是价格,运营的主要打法之一也是价格,所以是商品价格是一个在电商中很重要的一环。正因为如此也让促销演变的越来越复杂,那么如何在编码上花点心思来尽可能的降低业务的复杂化带来的影响和提高可扩展性来拥抱变化就变得很重要了。先从最简单的开始,我浏览了某东的促销,先把影响价格相关的几个促销找出来,暂时得出以下几个结论(这里又要提一下,我们实际工作中应在开始编码之前要做的就是和领域专家讨论促销的细节):

  1.满减:可以多个商品共同参与,汇总金额达到某个阈值之后减免XX金额。

  2.多买优惠(方式1):可以多个商品共同参与,汇总购买数量达到一定数量得到X折的优惠。

  3.多买优惠(方式2):可以多个商品共同参与,汇总购买数量达到一定数量减免最便宜的X件商品。

  4.限时折扣:直接商品的购买金额被修改到指定值。

  5.满减促销的金额满足点以优惠后价格为准,比如该商品既有限时折扣又有满减,则使用限时折扣的价格来计算金额满足点。

  6.优惠券是在之上的规则计算之后得出的金额基础下计算金额满足点。

  7.每一个商品的满减+多买优惠仅能参与一种。并且相同促销商品在购物车中商品展示的方式是在一组中。

   

三、建模

  根据上面的业务描述先找到其中的几个领域对象,然后在做一些适当的抽象,得出下面的UML图(点击图片可查看大图):

 

                             【图1】

四、实现

  建模完之后下面的事情就容易了,先梳理一下我们的业务处理顺序:

  1.根据购买上下文传入的购物车信息获取产品的相关促销。

  2.先处理单品促销。

  3.最后处理多商品共同参与的促销。

  梳理的过程中发现,为了能够实现满减和多买优惠促销仅能参与一个,所以需要再购买上下文和售价上下文之间传递购物项时增加一个参数选择的促销唯一标识(SelectedMultiProductsPromotionId)。

  随后根据上面业务处理顺序,发现整个处理的链路比较长,那么这里我决定定义一个值对象来承载整个处理的过程。如下:

public class BoughtProduct    {        private readonly List
_promotionRules = new List
(); public string ProductId { get; private set; } public int Quantity { get; private set; } public decimal UnitPrice { get; private set; } public decimal ReducePrice { get; private set; } ///
/// 商品在单品优惠后的单价,如果没有优惠则为正常购买的单价 /// public decimal DiscountedUnitPrice { get { return UnitPrice - ReducePrice; } } public decimal TotalDiscountedPrice { get { return DiscountedUnitPrice * Quantity; } } public ReadOnlyCollection
InSingleProductPromotionRules { get { return _promotionRules.OfType
().ToList().AsReadOnly(); } } public IMultiProductsPromotion InMultiProductPromotionRule { get; private set; } public BoughtProduct(string productId, int quantity, decimal unitPrice, decimal reducePrice, IEnumerable
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
().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
boughtProducts = new List
(); 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
fullGroupDtos = new List
(); 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 }; } }

 

五、结语

   这里的设计没有考虑促销规则的冲突问题,如果做的话把它放在创建促销规则的时候进行约束即可。

 

 

 

本文的源码地址:。

 

 

作者:

出处:

 

 

▶关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描右侧的二维码~。

定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。

 

如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「」,回复「技术」,送你一份我长期收集和整理的思维导图。

如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「」,回复「运营」,送你一份我长期收集和整理的思维导图。

转载地址:http://yzssl.baihongyu.com/

你可能感兴趣的文章
【下载】推荐一款免费的人脸识别SDK
查看>>
不定参数
查看>>
浏览器各种距离
查看>>
使用Python读取Google Spreadsheet的内容并写入到mangodb
查看>>
DOM操作和jQuery实现选项移动操作
查看>>
[emuch.net]MatrixComputations(1-6)
查看>>
ByteArrayOutputStream用法
查看>>
Floyed那些事~~~~~
查看>>
Python 学习笔记1 安装和IDE
查看>>
H5新增标签
查看>>
日志分析
查看>>
Extract Datasets
查看>>
递归加法运算
查看>>
蓝桥杯 倍数问题(dfs,枚举组合数)
查看>>
蓝桥杯 穿越雷区(bfs)
查看>>
SQL FORMAT() 函数实例
查看>>
iTerm 使用expect实现自动远程登录,登录跳板机
查看>>
JavaScript 面试:什么是纯函数?
查看>>
linux终端下查Dict.cn/WebsterOnline/Etymonline.com
查看>>
Hadoop(Pig)统计IP地理位置
查看>>