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

本系列所有文章

如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念

如何一步一步用DDD设计一个电商网站(二)—— 项目架构

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

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

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

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

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

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

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

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

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

如何一步一步用DDD设计一个电商网站(十二)—— 提交并生成订单

如何一步一步用DDD设计一个电商网站(十三)—— 领域事件扩展

 

 

 

阅读目录

 

一、前言

  之前的文章中已经涉及到了购买商品加入购物车,购物车内购物项的金额计算等功能。本篇准备把剩下的购物车的基本概念一次处理完。

 

二、回顾

  在动手之前我对之前的购买上下文内对象做了一次回顾。先梳理一下已经在上下文内出现的领域对象,如图1所示:

 

                          【图1】

  在梳理的过程中,我把原来Cart.AddCartItem(string productId, int quantity, decimal price)重构为了Cart.AddCartItem(Product product, int quantity),这样的好处的是2个:

  1.更清晰的表述出了在购物车中添加商品的意思。

  2.约束了外部只能通过Product对象来进行商品的添加,这样在Product构造函数中的约束在这里无需再次验证(如salename不能空等)。

 

三、梳理

  目前的购物车中在操作上的方法只有一个。参照目前主流电商平台的设计,我们需要增加:

  1.修改数量

  2.删除

  3.选择参与的促销(如果存在多个非单品级促销)

  4.收藏商品

  前面3个比较简单,都是购物车自身的概念,只有其中第四点超出了购物车自身的范畴,并且笔者认为收藏本就不是购物车特有的概念,而是在任何看得到商品的地方都可以做添加收藏的操作。那么自然引出了一个新的概念——收藏夹。看下最新的UML图,如图2所示:

 

                          【图2】

  我想会有一部分同学在设计收藏夹(Favorites)的时候会以另外的方式来做,比如像下图3这样:

                          【图3】

  这里我认为这样考虑的原因可能是由于DBFirst的思想导致的,因为图2中的“收藏夹”仅仅是维护了一个“用户”与“收藏项”之间的关系,那么只要在“收藏项”上增加一个UserId就直接可以省去了这一层关系,并且数据结构更加简单。这时候我们就需要注意了,千万不能有DBFirst思想去影响领域的建模,这样的方式会把“添加购物项”这类的业务含义泄露到了Repository层或者Application层去实现,导致无法用通用语言进行完整的业务描述了。

  并且在这个场景下,我个人观点认为,收藏商品其实只是为商品的展示途径中增加了一种途径而已,所以它应该被设计为独立存在的,由它自身来管理这些“被收藏的商品”,它的存在与否都不影响其它领域对象。

 

四、实现

  要实现这4个操作,那么需要在ICartService中增加下面4个接口:

        Result ChangeQuantity(string userId, string id, int quantity);

        Result DeleteCartItem(string userId, string id);

        Result AddToFavorites(string userId, string productId);

        Result ChangeMultiProductsPromotion(string userId, string productId, string selectedMultiProductsPromotionId);

  其中的部分实现如下:

        public Result AddToFavorites(string userId, string productId)
        {
            var cart = _confirmUserCartExistedDomainService.GetUserCart(userId);

            if (cart.IsEmpty())
            {
                return Result.Fail("当前购物车中并没有商品");
            }

            var cartItem = cart.GetCartItem(productId);
            if (cartItem == null)
            {
                return Result.Fail("该购物项已不存在");
            }

            var favorites = DomainRegistry.FavoritesRepository().GetByUserId(userId) ?? new Favorites(userId, null);
            favorites.AddFavoritesItem(cartItem);
            DomainRegistry.FavoritesRepository().Save(favorites);
            return Result.Success();
        }

  其中关于Favorites的构造函数我是这么做的:

        public Favorites(string userId, IEnumerable<FavoritesItem> favoritesItems)
        {
            if (string.IsNullOrWhiteSpace(userId))
                throw new ArgumentNullException("userId");
            this.UserId = userId;
            this._favoritesItems = new List<FavoritesItem>();

            if (favoritesItems != null && favoritesItems.Any())
            {
                foreach (var favoritesItem in favoritesItems)
                {
                    AddFavoritesItem(favoritesItem);
                }
            }
        }

  这样可以重用AddFavoritesItem中的一些守卫操作,保证在业务产生变动之后历史数据从DB取出来的时候经过一次最新的业务验证,确保数据在流转过程中的合法性。这个方式可以择机运用在任何聚合的构造函数中。

 

五、结语

  本篇主要的观点还是在建模上的思维惯性,抛开DB,抛开DB,抛开DB,重要的事情说3遍。

   

 

 

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

 

作者:Zachary
出处:https://zacharyfan.com/archives/180.html

 

 

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

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

 

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

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

posted @ 2017-01-05 06:32  Zachary_Fan  阅读(9059)  评论(15编辑  收藏