DDD设计一个电商网站(十一)—— 最后的准备

 

 阅读目录

一、前言

  最近实在太忙,上周停更了一周。按流程一步一步走到现在,到达了整个下单流程的最后一公里——结算页的处理。从整个流程来看,这里需要用户填写的信息是最多的,那么在后端的设计中如何考虑到业务边界的划分,和相互之间的交互复杂度,又是我们需要考虑的地方。总体来说本篇讲述的内容在前几篇都有涉及,所以这次一次性处理的业务比较多,已经比较熟练的看官可以跳过本篇。

二、准备

  主流的电商设计中结算页包含以下5个概念:选择收货地址、选择支付方式、选择快递、使用优惠券、使用余额和积分。笔者认为,根据我们在本系列的第一篇博文中的上下文映射图,这背后涉及到了多个上下文的协作:

  1.用户上下文:包含选择收货地址

  2.支付上下文:包含选择支付方式、使用余额和积分

  3.售价上下文:使用优惠券。

  其中第“1”点我的理解是在整个大系统中,收货地址并不是仅在购买的时候会用到,而是用户可以直接管理的(一般主流电商都可以在《用户中心》菜单内操作个人的收货地址信息),在购物车中进行管理其实并不是一个必须经过的流程,大部分场景下只是在现有地址中做一个选择,所以收货地址更接近于用户域而不是购买域,在购物车的管理可以理解为一个快捷方式而已。

  第“2”点,我的理解是,把支付操作相关的概念放到一起,可以做的很灵活,可以和运营打法搭配起来。如:支付方式和使用积分的联动、像天猫那样的红包等促进用户购买欲望的招式。

  第“3”点,我的理解是,优惠券也是会影响到整个商品的售价的,所以它应该属于售价上下文,配合其它的促销方式做出更多的打法。

  剩下的快递我认为是本地购买上下文内的概念,因为它只服务于购买的流程之中。

三、实现

  根据服务能力来编写ApplicationService,那么这里总共是提供了3种服务能力,所以定义了3个ApplicationService来提供这些功能:

  1.IDeliveryService:其中包含选择收货地址和选择快递

  2.IPaymentService:其中包含选择支付方式、使用余额和积分

  3.ICouponService:包含选择礼券。

  好了接下来就是其中涉及到的领域模型的设计,这里需要纠正一个之前的错误,在之前的设计中把余额直接放到了User这个值对象中,并且是从用户上下文获取的,现在看看当初的设计不是很妥当。因为余额并不是用户与生俱来的东西,就好比我要认识一个人,并不一定要知道他有多少钱,但是必然需要知道姓名、年龄等。所以余额与用户之间并不是一个强依赖关系。而且分属于2个不同的领域聚合、甚至是上下文。这里涉及的所有领域模型的UML图如下图1所示:

                          【图1】

  其中的值对象都是从远程上下文获取的,所以这里在购买上下文里只是使用了其的一个副本。在购买上下文的3个ApplicationService如下:

  1. public interface IDeliveryService
  2. {
  3. List<ShippingAddressDTO> GetAllShippingAddresses(string userId);
  4.  
  5. Result AddNewShippingAddress(string userId, DeliveryAddNewShippingAddressRequest request);
  6.  
  7. Result EditShippingAddress(string userId, DeliveryEditShippingAddressRequest request);
  8.  
  9. Result DeleteShippingAddress(string id);
  10.  
  11. List<ExpressDTO> GetAllCanUseExpresses();
  12. }
  1. public interface IPaymentService
  2. {
  3. List<PaymentMethodDTO> GetAllCanUsePaymentMethods();
  4.  
  5. WalletDTO GetUserWallet(string userId);
  6. }
  1. public interface ICouponService
  2. {
  3. List<CouponDTO> GetAllCoupons(string userId);
  4. }

  这里接口定义思路是把界面上的操作记录全部由UI程序做本地缓存/Cookie等,减少服务端的处理压力,所以接口看上去比较简单,没有那些使用礼券,修改使用的收货地址这类的接口。

  另外提一下,在当前的解决方案中的售价上下文中的处理中,增加了2个聚合来处理优惠券相关的业务。

  1. public class Coupon : AggregateRoot
  2. {
  3. public string Name { get; private set; }
  4.  
  5. public decimal Value { get; private set; }
  6.  
  7. public DateTime ExpiryDate { get; private set; }
  8.  
  9. public List<string> ContainsProductIds { get; private set; }
  10.  
  11. public Coupon(string name, decimal value, DateTime expiryDate, IEnumerable<string> containsProductIds)
  12. {
  13. if (string.IsNullOrWhiteSpace(name))
  14. throw new ArgumentNullException("name");
  15.  
  16. if (value <= 0)
  17. throw new ArgumentException("value不能小于等于0", "value");
  18.  
  19. if (expiryDate == default(DateTime))
  20. throw new ArgumentException("请传入正确的expiryDate", "expiryDate");
  21.  
  22. if (containsProductIds == null)
  23. throw new ArgumentNullException("containsProductIds");
  24.  
  25. this.Name = name;
  26. this.Value = value;
  27. this.ExpiryDate = expiryDate;
  28. this.ContainsProductIds = containsProductIds.ToList();
  29. }
  30. }
  1. public class CouponNo : AggregateRoot
  2. {
  3. public string CouponId { get; private set; }
  4.  
  5. public DateTime UsedTime { get; private set; }
  6.  
  7. public bool IsUsed
  8. {
  9. get { return UsedTime != default(DateTime) && UsedTime < DateTime.Now; }
  10. }
  11.  
  12. public string UserId { get; private set; }
  13.  
  14. public CouponNo(string couponId, DateTime usedTime, string userId)
  15. {
  16. if (string.IsNullOrWhiteSpace(couponId))
  17. throw new ArgumentNullException("couponId");
  18.  
  19. if (string.IsNullOrWhiteSpace(userId))
  20. throw new ArgumentNullException("userId");
  21.  
  22. this.CouponId = couponId;
  23. this.UsedTime = usedTime;
  24. this.UserId = userId;
  25. }
  26.  
  27. public void BeUsed()
  28. {
  29. this.UsedTime = DateTime.Now;
  30. }
  31. }

  其中CouponNo中的CouponId是保持了一个对Coupon聚合ID的引用,在需要的时候从Repository中取出Coupon的信息。部分代码如下:

  1. var couponNos = DomainRegistry.CouponNoRepository().GetNotUsedByUserId(cart.UserId);
  2.  
  3. var buyProductIds = cart.CartItems.Select(ent => ent.ProductId);
  4. List<CouponDTO> couponDtos = new List<CouponDTO>();
  5. foreach (var couponNo in couponNos)
  6. {
  7. if (couponNo.IsUsed)
  8. continue;
  9.  
  10. var coupon = DomainRegistry.CouponRepository().GetByIdentity(couponNo.CouponId);
  11.  
  12. if (coupon.ContainsProductIds.Count == 0 || coupon.ContainsProductIds.Any(ent => buyProductIds.Any(e => e == ent)))
  13. {
  14. couponDtos.Add(new CouponDTO
  15. {
  16. CanUse = couponNo.IsUsed,
  17. ExpiryDate = coupon.ExpiryDate,
  18. ID = couponNo.ID,
  19. Name = coupon.Name,
  20. Value = coupon.Value
  21. });
  22. }
  23. }

四、结语

  本篇比较简单不多述了,下面源码奉上,有兴趣的同学自行下载查看全部源码。

本文的源码地址:https://github.com/ZacharyFan/DDDDemo/tree/Demo11

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

DDD设计一个电商网站的更多相关文章

  1. 如何一步一步用DDD设计一个电商网站(九)—— 小心陷入值对象持久化的坑

    阅读目录 前言 场景1的思考 场景2的思考 避坑方式 实践 结语 一.前言 在上一篇中(如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成),有一行注释的代码: public interfa ...

  2. 如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成

    阅读目录 前言 建模 实现 结语 一.前言 前面几篇已经实现了一个基本的购买+售价计算的过程,这次再让售价丰满一些,增加一个会员价的概念.会员价在现在的主流电商中,是一个不大常见的模式,其带来的问题是 ...

  3. 如何一步一步用DDD设计一个电商网站(十)—— 一个完整的购物车

     阅读目录 前言 回顾 梳理 实现 结语 一.前言 之前的文章中已经涉及到了购买商品加入购物车,购物车内购物项的金额计算等功能.本篇准备把剩下的购物车的基本概念一次处理完. 二.回顾 在动手之前我对之 ...

  4. 如何一步一步用DDD设计一个电商网站(七)—— 实现售价上下文

    阅读目录 前言 明确业务细节 建模 实现 结语 一.前言 上一篇我们已经确立的购买上下文和销售上下文的交互方式,传送门在此:http://www.cnblogs.com/Zachary-Fan/p/D ...

  5. 如何一步一步用DDD设计一个电商网站(六)—— 给购物车加点料,集成售价上下文

    阅读目录 前言 如何在一个项目中实现多个上下文的业务 售价上下文与购买上下文的集成 结语 一.前言 前几篇已经实现了一个最简单的购买过程,这次开始往这个过程中增加一些东西.比如促销.会员价等,在我们的 ...

  6. 如何一步一步用DDD设计一个电商网站(五)—— 停下脚步,重新出发

    阅读目录 前言 单元测试 纠正错误,重新出发 结语 一.前言 实际编码已经写了2篇了,在这过程中非常感谢有听到观点不同的声音,借着这个契机,今天这篇就把大家提出的建议一个个的过一遍,重新整理,重新出发 ...

  7. 如何一步一步用DDD设计一个电商网站(四)—— 把商品卖给用户

    阅读目录 前言 怎么卖 领域服务的使用 回到现实 结语 一.前言 上篇中我们讲述了“把商品卖给用户”中的商品和用户的初步设计.现在把剩余的“卖”这个动作给做了.这里提醒一下,正常情况下,我们的每一步业 ...

  8. 如何一步一步用DDD设计一个电商网站(三)—— 初涉核心域

    一.前言 结合我们本次系列的第一篇博文中提到的上下文映射图(传送门:如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念),得知我们这个电商网站的核心域就是销售子域.因为电子商务是以信息网络 ...

  9. 如何一步一步用DDD设计一个电商网站(十一)—— 最后的准备

     阅读目录 前言 准备 实现 结语 一.前言 最近实在太忙,上周停更了一周.按流程一步一步走到现在,到达了整个下单流程的最后一公里——结算页的处理.从整个流程来看,这里需要用户填写的信息是最多的,那么 ...

随机推荐

  1. 一个有意思的 hta 程序 (html application)

    哈哈,刚才同事给我讲了一个hta 程序,他自己说最近在学html5 开发坦克大战,不错,这种好奇心, 好学的精神值得我这个程序员学习,感觉他的视野面比我这个程序员还广,有点小惭愧. 什么是hta 呢? ...

  2. 一个java解析xml的简单例子

    java解析xml,主要是通过Dom4j实现的,很多场合都会用到此功能,需要解析XML文件. 下面是一个简单的解析XML文件的例子: import java.util.Iterator; import ...

  3. Android复习--广播

    广播有两种方式,一种静态广播,一种动态广播. 静态广播-->静态广播接收器在配置文件里面注册. 动态广播-->而动态广播接收器在代码里面注册. 广播的发送: Context.sendBro ...

  4. stdarg.h头

    stdarg.h 头文件,主要目的是让函数可以接受可变参数. va_list :用来保存宏va_arg与宏va_end所需信息. va_start :使va_list指向起始的参数 va_arg :检 ...

  5. CG之refract函数简单实现

    CG的refract函数定义如下: refract(I, N, eta) 根据入射光线方向I,表面法向量N和折射相对系数eta,计算折射向量.如果对给定的eta,I和N之间的角度太大,返回(0,0,0 ...

  6. HDU 5171 GTY's birthday gift 矩阵快速幂

    GTY's birthday gift Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Othe ...

  7. iframe载入等待

    <style> #pageloading{position:absolute; left:0px; top:0px;background:white url('../images/load ...

  8. x86_64是什么意思

    x86指的是32位计算机的架构,也指32位的操作系统,比如i386,i686,i486等:x86_64和x64指的都是64位架构,也指64位操作系统

  9. ELK 日志分析体系

    ELK   日志分析体系 ELK 是指 Elasticsearch.Logstash.Kibana三个开源软件的组合. logstash                       负责日志的收集,处 ...

  10. 模块之dir函数

    dir()函数你可以使用内建的dir函数来列出模块定义的标识符.标识符有函数.类和变量.当你为dir()提供一个模块名的时候,它返回模块定义的名称列表.如果不提供参数,它返回当前模块中定义的名称列表. ...