zoukankan      html  css  js  c++  java
  • DDD设计一个电商网站

    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)冒泡排序
    淘宝爬虫
    爬虫_豆瓣电影top250 (正则表达式)
    爬虫_猫眼电影top100(正则表达式)
    Android 简单调用摄像头
    Android 简单天气预报
    思维模型
    This view is not constrained, it only has designtime positions, so it will jump to (0,0) unless you
    Android studio preview界面无法预览,报错render problem
    Android studio 3.1.2报错,no target device found
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/6288383.html
Copyright © 2011-2022 走看看