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

阅读目录

  • 前言
  • 单元测试
  • 纠正错误,重新出发
  • 结语

一、前言

  实际编码已经写了2篇了,在这过程中非常感谢有听到观点不同的声音,借着这个契机,今天这篇就把大家提出的建议一个个的过一遍,重新整理,重新出发,为了让接下去的DDD之路走的更好。

二、单元测试

  蟋蟀兄在我的第三篇文章下面指出:

  

  这点其实是我偷懒了,单元测试其实不单单在DDD中是一个很重要的一环,在我们崇尚敏捷,快速迭代的大背景下,有良好的单元测试模块可以保证快速迭代下的项目质量。有甚至可以使用测试先行的TDD模式。

  单元测试的好处我就不多说了,那么现在开始在项目中增加单元测试。单元测试有多种命名方式,我个人的方式是给每一个对象单独建立一个测试类,然后里面每个单元测试方法的命名规则为"方法名_条件_预期的结果"这样子。那么根据我们之前的Cart和CartItem的建模,编写的单元测试如下:

    [TestClass]
    public class CartTest
    {
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void Constructor_CartIdDefault_ThrowArgumentException()
        {
            var cart = new Cart(default(Guid), Guid.NewGuid(), DateTime.Now);
            Assert.AreNotEqual(null, cart);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void Constructor_UserIdDefault_ThrowArgumentException()
        {
            var cart = new Cart(Guid.NewGuid(), default(Guid), DateTime.Now);
            Assert.AreNotEqual(null, cart);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void Constructor_LastChangeTimeDefault_ThrowArgumentException()
        {
            var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), default(DateTime));
            Assert.AreNotEqual(null, cart);
        }

        [TestMethod]
        public void AddCartItem_NotExisted_TotalItemCountIsIncreased()
        {
            var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
            cart.AddCartItem(new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100));
            Assert.AreEqual(1, cart.TotalItemCount());

            cart.AddCartItem(new CartItem(new Guid("22222222-2222-2222-2222-222222222222"), 1, 100));
            Assert.AreEqual(2, cart.TotalItemCount());
        }

        [TestMethod]
        public void AddCartItem_Existed_TotalItemCountIsNotIncreasedTotalItemNumIsIncreased()
        {
            var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
            cart.AddCartItem(new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100));
            Assert.AreEqual(1, cart.TotalItemCount());
            Assert.AreEqual(1, cart.TotalItemNum());

            cart.AddCartItem(new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100));
            Assert.AreEqual(1, cart.TotalItemCount());
            Assert.AreEqual(2, cart.TotalItemNum());
        }
    }
    [TestClass]
    public class CartItemTest
    {
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void ModifyQuantity_LessZero_ThrowArgumentException()
        {
            var cartItem = new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
            cartItem.ModifyQuantity(-1);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void ModifyQuantity_EqualsZero_ThrowArgumentException()
        {
            var cartItem = new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
            cartItem.ModifyQuantity(0);
        }

        [TestMethod]
        public void ModifyQuantity_MoreZero_Success()
        {
            var cartItem = new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
            cartItem.ModifyQuantity(10);
            Assert.AreEqual(10, cartItem.Quantity);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void ModifyPrice_LessZero_ThrowArgumentException()
        {
            var cartItem = new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
            cartItem.ModifyPrice(-1);
        }

        [TestMethod]
        public void ModifyQuantity_EqualsZero_Success()
        {
            var cartItem = new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
            cartItem.ModifyQuantity(0);
            Assert.AreEqual(0, cartItem.Price);
        }

        [TestMethod]
        public void ModifyQuantity_MoreZero_Success()
        {
            var cartItem = new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
            cartItem.ModifyQuantity(10);
            Assert.AreEqual(10, cartItem.Price);
        }
    }

三、纠正错误,重新出发

  在写CartItemTest的时候发现了一个问题。领域对象的设计中有一个要点,就是实体必须需要通过其所属的聚合根才能访问,这样才能体现出聚合的的整体性,并且减少外界对聚合内部过多的了解。而目前对于CartItem的运用却有些背道而驰的意思,由外部对象进行实例化,必然增加了外部调用方对整个购物项构造过程的了解。有一位园友tubo有提到这点。

   我思考了下,觉得这位园友的建议是对的。他建议的改法恰恰能够满足这个要求,隐藏了构造CartItem实体的细节。
  好了那先把CartItem的构造函数访问类型设置为internal吧,这样也只能在CartItem所在的Domain项目中进行实例化了,然后再修改Cart.AddCartItem方法的参数。变为如下:

        public void AddCartItem(Guid productId, int quantity, decimal price)
        {
            var cartItem = new CartItem(productId, quantity, price);
            var existedCartItem = this._cartItems.FirstOrDefault(ent => ent.ProductId == cartItem.ProductId);
            if (existedCartItem == null)
            {
                this._cartItems.Add(cartItem);
            }
            else
            {
                existedCartItem.ModifyPrice(cartItem.Price); //有可能价格更新了,每次都更新一下。
                existedCartItem.ModifyQuantity(existedCartItem.Quantity + cartItem.Quantity);
            }
        }

单元测试也做出相应的更改:

    [TestClass]
    public class CartTest
    {
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void Constructor_CartIdDefault_ThrowArgumentException()
        {
            var cart = new Cart(default(Guid), Guid.NewGuid(), DateTime.Now);
            Assert.AreNotEqual(null, cart);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void Constructor_UserIdDefault_ThrowArgumentException()
        {
            var cart = new Cart(Guid.NewGuid(), default(Guid), DateTime.Now);
            Assert.AreNotEqual(null, cart);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void Constructor_LastChangeTimeDefault_ThrowArgumentException()
        {
            var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), default(DateTime));
            Assert.AreNotEqual(null, cart);
        }

        [TestMethod]
        public void AddCartItem_NotExisted_TotalItemCountIsIncreased()
        {
            var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
            cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
            Assert.AreEqual(1, cart.TotalItemCount());

            cart.AddCartItem(new Guid("22222222-2222-2222-2222-222222222222"), 1, 100);
            Assert.AreEqual(2, cart.TotalItemCount());
        }

        [TestMethod]
        public void AddCartItem_Existed_TotalItemCountIsNotIncreasedTotalItemNumIsIncreased()
        {
            var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
            cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
            Assert.AreEqual(1, cart.TotalItemCount());
            Assert.AreEqual(1, cart.TotalItemNum());

            cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
            Assert.AreEqual(1, cart.TotalItemCount());
            Assert.AreEqual(2, cart.TotalItemNum());
        }
    }
    [TestClass]
    public class CartItemTest
    {
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void ModifyQuantity_LessZero_ThrowArgumentException()
        {
            var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
            cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
            var cartItem = cart.GetCartItem(new Guid("11111111-1111-1111-1111-111111111111"));
            Assert.AreNotEqual(null, cartItem);
            Assert.AreEqual(1, cartItem.Quantity);
            cartItem.ModifyQuantity(-1);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void ModifyQuantity_EqualsZero_ThrowArgumentException()
        {
            var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
            cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
            var cartItem = cart.GetCartItem(new Guid("11111111-1111-1111-1111-111111111111"));
            Assert.AreNotEqual(null, cartItem);
            Assert.AreEqual(1, cartItem.Quantity);
            cartItem.ModifyQuantity(0);
        }

        [TestMethod]
        public void ModifyQuantity_MoreZero_Success()
        {
            var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
            cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
            var cartItem = cart.GetCartItem(new Guid("11111111-1111-1111-1111-111111111111"));
            Assert.AreNotEqual(null, cartItem);
            Assert.AreEqual(1, cartItem.Quantity);
            cartItem.ModifyQuantity(10);
            Assert.AreEqual(10, cartItem.Quantity);
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void ModifyPrice_LessZero_ThrowArgumentException()
        {
            var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
            cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
            var cartItem = cart.GetCartItem(new Guid("11111111-1111-1111-1111-111111111111"));
            Assert.AreNotEqual(null, cartItem);
            Assert.AreEqual(100, cartItem.Price);
            cartItem.ModifyPrice(-1);
        }

        [TestMethod]
        public void ModifyPrice_EqualsZero_Success()
        {
            var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
            cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
            var cartItem = cart.GetCartItem(new Guid("11111111-1111-1111-1111-111111111111"));
            Assert.AreNotEqual(null, cartItem);
            Assert.AreEqual(100, cartItem.Price);
            cartItem.ModifyPrice(0);
            Assert.AreEqual(0, cartItem.Price);
        }

        [TestMethod]
        public void ModifyPrice_MoreZero_Success()
        {
            var cart = new Cart(Guid.NewGuid(), Guid.NewGuid(), DateTime.Now);
            cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), 1, 100);
            var cartItem = cart.GetCartItem(new Guid("11111111-1111-1111-1111-111111111111"));
            Assert.AreNotEqual(null, cartItem);
            Assert.AreEqual(100, cartItem.Price);
            cartItem.ModifyPrice(10);
            Assert.AreEqual(10, cartItem.Price);
        }
    }

  这样一来,被玻璃鱼儿netfocus2位园友所指出的奇怪的“UserBuyProductDomainService”也自然消失了。应用层代码变成:

        public Result Buy(Guid userId, Guid productId, int quantity)
        {
            var product = DomainRegistry.ProductService().GetProduct(productId);
            if (product == null)
            {
                return Result.Fail("对不起,未能获取产品信息请重试~");
            }

            var cart = _getUserCartDomainService.GetUserCart(userId);
            cart.AddCartItem(productId, quantity, product.SalePrice);
            DomainRegistry.CartRepository().Save(cart);
            return Result.Success();
        }

四、结语

  DDD的道路是坎坷的,我希望通过在园子里发布的文章能够结交到志同道合的DDD之友,欢迎大家不吝啬自己的见解,多多留言,也让想学习或者正在学习DDD的园友少走一些弯路。

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

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

时间: 2024-10-31 01:16:34

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

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

阅读目录 前言 如何在一个项目中实现多个上下文的业务 售价上下文与购买上下文的集成 结语 一.前言 前几篇已经实现了一个最简单的购买过程,这次开始往这个过程中增加一些东西.比如促销.会员价等,在我们的第一篇文章(如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念)中规划的上下文映射图可以看到,这些都属于一个独立的上下文(售价上下文). 二.如何在一个项目中实现多个上下文的业务 一般情况下,为了更好的分而治之,把不同的上下文作为单独的service,然后通过rpc框架(如WCF)来对其

DDD设计一个电商网站

DDD设计一个电商网站(十一)-- 最后的准备  阅读目录 前言 准备 实现 结语 一.前言 最近实在太忙,上周停更了一周.按流程一步一步走到现在,到达了整个下单流程的最后一公里--结算页的处理.从整个流程来看,这里需要用户填写的信息是最多的,那么在后端的设计中如何考虑到业务边界的划分,和相互之间的交互复杂度,又是我们需要考虑的地方.总体来说本篇讲述的内容在前几篇都有涉及,所以这次一次性处理的业务比较多,已经比较熟练的看官可以跳过本篇. 二.准备 主流的电商设计中结算页包含以下5个概念:选择收货

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

阅读目录 前言 建模 实现 结语 一.前言 前面几篇已经实现了一个基本的购买+售价计算的过程,这次再让售价丰满一些,增加一个会员价的概念.会员价在现在的主流电商中,是一个不大常见的模式,其带来的问题是: 1.加大了运营的复杂度,会员价如何与促销结合,比如应在折前运用还是折后运用等. 2.如果是折前那么需要考虑满减类型促销的金额满足点门槛反而相对来说是提高了. 3.如果是折后那么享受了多重优惠,成本控制的时候需要考虑进去. 在我们这个练手的Demo中暂时决定让会员价在折后运用,并且仅在不满足满减促

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

阅读目录 前言 怎么卖 领域服务的使用 回到现实 结语 一.前言 上篇中我们讲述了“把商品卖给用户”中的商品和用户的初步设计.现在把剩余的“卖”这个动作给做了.这里提醒一下,正常情况下,我们的每一步业务设计都需要和领域专家进行沟通,尽可能的符合通用语言的表述.这里的领域专家包括但不限于当前开发团队中对这块业务最了解的开发人员.系统实际的使用人等. 二.怎么卖 如果在没有结合当前上下文的情况下,用通用语言来表述,我们很容易把代码写成下面的这个样子(其中DomainRegistry只是一个简单的工厂

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

 阅读目录 前言 回顾 梳理 实现 结语 一.前言 之前的文章中已经涉及到了购买商品加入购物车,购物车内购物项的金额计算等功能.本篇准备把剩下的购物车的基本概念一次处理完. 二.回顾 在动手之前我对之前的购买上下文内对象做了一次回顾.先梳理一下已经在上下文内出现的领域对象,如图1所示: [图1] 在梳理的过程中,我把原来Cart.AddCartItem(string productId, int quantity, decimal price)重构为了Cart.AddCartItem(Produ

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

阅读目录 前言 明确业务细节 建模 实现 结语 一.前言 上一篇我们已经确立的购买上下文和销售上下文的交互方式,传送门在此:http://www.cnblogs.com/Zachary-Fan/p/DDD_6.html,本篇我们来实现售价上下文的具体细节. 二.明确业务细节 电商市场越来越成熟,竞争也越来越激烈,影响客户流量的关键因素之一就是价格,运营的主要打法之一也是价格,所以是商品价格是一个在电商中很重要的一环.正因为如此也让促销演变的越来越复杂,那么如何在编码上花点心思来尽可能的降低业务的

系统设计题:如何设计一个电商平台积分兑换系统!

1.拉开差距的一类面试题 现在面试经常会遇到一类问题,面试官让你现场设计出某个业务场景下的一个系统,这个系统往往在业务或者技术上有一定难度,主要考察的是你多年积淀下来的系统设计的能力以及技术思维的能力. 类似的这类系统设计题目很多,比如: 请你设计一个秒杀系统 请你设计一个支撑百万用户的IM消息系统 请你设计一个微信红包系统 请你设计一个电商平台积分兑换系统 这些题目本身都是开放式命题,没有固定答案.遇到这种问题,一定不要慌,关键是在现场要思路清楚,有理有据,慢慢分析. 本文就其中一个问题:设计

设计一个电商平台的积分兑换系统

1.业务需求的描述 假设面试官现在给出来对于这个电商平台的积分兑换系统的相关需求如下: 用户在电商平台里平时通过购买商品.晒单评论可以有不断的积累积分积累到足够的积分后,就可以在电商平台的积分兑换页面中,选择使用自己的积分来兑换一些礼品. 需求其实就这么简单,那么面试官说了,针对这个业务场景给出你对这个机制实现的思考过程以及这里要注意的一些地方. 2.对业务流程的思考 如何思考?首先,用户不停的购买商品以及晒单评论,会不断的获取积分,那么是不是需要一张积分表,专门用来存储每个用户的积分呢?没错,

分布式架构设计之电商平台

分布式架构设计之电商平台 何为软件架构?不同人的答案会有所不同,而我认为一个好的软件架构除了要具备业务功能外,还应该具备一定的高性能.高可用.高伸缩性及可拓展等非功能需求.而软件架构是由业务架构和技术架构两部分组成,因为有了业务结构才会催生出软件架构,进而来满足业务上的需求,所以,在做软件架构设计时,需要分为业务架构设计和技术软件架构设计,二者不可分离哦!那么,接下来就以本人实际工作中的电商平台为例,进行说明电商平台架构设计,因为不同行业产品系统不同业务不同,而催生的系统软件的实现要求及架构设计