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如下:

    public interface IDeliveryService
{
List<ShippingAddressDTO> GetAllShippingAddresses(string userId); Result AddNewShippingAddress(string userId, DeliveryAddNewShippingAddressRequest request); Result EditShippingAddress(string userId, DeliveryEditShippingAddressRequest request); Result DeleteShippingAddress(string id); List<ExpressDTO> GetAllCanUseExpresses();
}
    public interface IPaymentService
{
List<PaymentMethodDTO> GetAllCanUsePaymentMethods(); WalletDTO GetUserWallet(string userId);
}
    public interface ICouponService
{
List<CouponDTO> GetAllCoupons(string userId);
}

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

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

    public class Coupon : AggregateRoot
{
public string Name { get; private set; } public decimal Value { get; private set; } public DateTime ExpiryDate { get; private set; } public List<string> ContainsProductIds { get; private set; } public Coupon(string name, decimal value, DateTime expiryDate, IEnumerable<string> containsProductIds)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentNullException("name"); if (value <= 0)
throw new ArgumentException("value不能小于等于0", "value"); if (expiryDate == default(DateTime))
throw new ArgumentException("请传入正确的expiryDate", "expiryDate"); if (containsProductIds == null)
throw new ArgumentNullException("containsProductIds"); this.Name = name;
this.Value = value;
this.ExpiryDate = expiryDate;
this.ContainsProductIds = containsProductIds.ToList();
}
}
    public class CouponNo : AggregateRoot
{
public string CouponId { get; private set; } public DateTime UsedTime { get; private set; } public bool IsUsed
{
get { return UsedTime != default(DateTime) && UsedTime < DateTime.Now; }
} public string UserId { get; private set; } public CouponNo(string couponId, DateTime usedTime, string userId)
{
if (string.IsNullOrWhiteSpace(couponId))
throw new ArgumentNullException("couponId"); if (string.IsNullOrWhiteSpace(userId))
throw new ArgumentNullException("userId"); this.CouponId = couponId;
this.UsedTime = usedTime;
this.UserId = userId;
} public void BeUsed()
{
this.UsedTime = DateTime.Now;
}
}

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

            var couponNos = DomainRegistry.CouponNoRepository().GetNotUsedByUserId(cart.UserId);

            var buyProductIds = cart.CartItems.Select(ent => ent.ProductId);
List<CouponDTO> couponDtos = new List<CouponDTO>();
foreach (var couponNo in couponNos)
{
if (couponNo.IsUsed)
continue; var coupon = DomainRegistry.CouponRepository().GetByIdentity(couponNo.CouponId); if (coupon.ContainsProductIds.Count == 0 || coupon.ContainsProductIds.Any(ent => buyProductIds.Any(e => e == ent)))
{
couponDtos.Add(new CouponDTO
{
CanUse = couponNo.IsUsed,
ExpiryDate = coupon.ExpiryDate,
ID = couponNo.ID,
Name = coupon.Name,
Value = coupon.Value
});
}
}

四、结语

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

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

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

最新文章

  1. 《Unix网络编程》卷一(简介TCP/IP、基础套接字编程)
  2. 【Vijos】1218 数字游戏
  3. In place Merge(原地归并)
  4. C# Sending data using GET or POST ZZ
  5. 【Linux探索之旅】开宗明义+第一部分第一课:什么是Linux?
  6. view 上推效果
  7. NOIP2014-普及组复赛-第四题-子矩阵
  8. java 接口与实现
  9. MRC、ARC内存管理机制
  10. Hibernate基础知识总结
  11. pyspark进行词频统计并返回topN
  12. Dynamics 365设置错误通知首选项的方法
  13. 【原创】大数据基础之Hadoop(1)HA实现原理
  14. topological sort
  15. webstorm编辑器相关
  16. 关于ava容器、队列,知识点总结
  17. [转载]Memory Limits for Windows and Windows Server Releases
  18. React learn path
  19. day10 文件处理指针使用 函数基本使用
  20. 如何在vue中全局引入stylus文件的公共变量

热门文章

  1. IE6下最小19px像素
  2. 《accelerated c++》第九章---设计类
  3. 微信小程序实例教程(三)
  4. STM32之FreeRTOS
  5. 【转贴】 Flex 预编译处理 与 手机项目、Web项目、Air项目自由转换
  6. 路过Haxe
  7. thinkphp中的ajax分页
  8. C语言实现求字符串子集问题
  9. HDU 4044 GeoDefense
  10. ZOJ 3537 Cake