本系列所有文章

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

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

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

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

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

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

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

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

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

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

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

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

如何一步一步用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"), , ));
Assert.AreEqual(, cart.TotalItemCount()); cart.AddCartItem(new CartItem(new Guid("22222222-2222-2222-2222-222222222222"), , ));
Assert.AreEqual(, 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"), , ));
Assert.AreEqual(, cart.TotalItemCount());
Assert.AreEqual(, cart.TotalItemNum()); cart.AddCartItem(new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), , ));
Assert.AreEqual(, cart.TotalItemCount());
Assert.AreEqual(, 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"), , );
cartItem.ModifyQuantity(-);
} [TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void ModifyQuantity_EqualsZero_ThrowArgumentException()
{
var cartItem = new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), , );
cartItem.ModifyQuantity();
} [TestMethod]
public void ModifyQuantity_MoreZero_Success()
{
var cartItem = new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), , );
cartItem.ModifyQuantity();
Assert.AreEqual(, cartItem.Quantity);
} [TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void ModifyPrice_LessZero_ThrowArgumentException()
{
var cartItem = new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), , );
cartItem.ModifyPrice(-);
} [TestMethod]
public void ModifyQuantity_EqualsZero_Success()
{
var cartItem = new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), , );
cartItem.ModifyQuantity();
Assert.AreEqual(, cartItem.Price);
} [TestMethod]
public void ModifyQuantity_MoreZero_Success()
{
var cartItem = new CartItem(new Guid("11111111-1111-1111-1111-111111111111"), , );
cartItem.ModifyQuantity();
Assert.AreEqual(, 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"), , );
Assert.AreEqual(, cart.TotalItemCount()); cart.AddCartItem(new Guid("22222222-2222-2222-2222-222222222222"), , );
Assert.AreEqual(, 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"), , );
Assert.AreEqual(, cart.TotalItemCount());
Assert.AreEqual(, cart.TotalItemNum()); cart.AddCartItem(new Guid("11111111-1111-1111-1111-111111111111"), , );
Assert.AreEqual(, cart.TotalItemCount());
Assert.AreEqual(, 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"), , );
var cartItem = cart.GetCartItem(new Guid("11111111-1111-1111-1111-111111111111"));
Assert.AreNotEqual(null, cartItem);
Assert.AreEqual(, cartItem.Quantity);
cartItem.ModifyQuantity(-);
} [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"), , );
var cartItem = cart.GetCartItem(new Guid("11111111-1111-1111-1111-111111111111"));
Assert.AreNotEqual(null, cartItem);
Assert.AreEqual(, cartItem.Quantity);
cartItem.ModifyQuantity();
} [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"), , );
var cartItem = cart.GetCartItem(new Guid("11111111-1111-1111-1111-111111111111"));
Assert.AreNotEqual(null, cartItem);
Assert.AreEqual(, cartItem.Quantity);
cartItem.ModifyQuantity();
Assert.AreEqual(, 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"), , );
var cartItem = cart.GetCartItem(new Guid("11111111-1111-1111-1111-111111111111"));
Assert.AreNotEqual(null, cartItem);
Assert.AreEqual(, cartItem.Price);
cartItem.ModifyPrice(-);
} [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"), , );
var cartItem = cart.GetCartItem(new Guid("11111111-1111-1111-1111-111111111111"));
Assert.AreNotEqual(null, cartItem);
Assert.AreEqual(, cartItem.Price);
cartItem.ModifyPrice();
Assert.AreEqual(, 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"), , );
var cartItem = cart.GetCartItem(new Guid("11111111-1111-1111-1111-111111111111"));
Assert.AreNotEqual(null, cartItem);
Assert.AreEqual(, cartItem.Price);
cartItem.ModifyPrice();
Assert.AreEqual(, 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
出处:https://zacharyfan.com/archives/141.html

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

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

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

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

最新文章

  1. 【读书笔记】iOS-ARC-Xcode检测循环引用
  2. Java开发心得
  3. picturebox 图片自适应
  4. Linux Kernel 排程機制介紹
  5. 分布式事务 & 两阶段提交 & 三阶段提交
  6. WPF 自定义图片按钮
  7. unittest批量执行测试用例
  8. Linux命令大全 欢迎补充 评论添加~
  9. ROC曲线-阈值评价标准
  10. 批量将某一目录下的.py文件改为.txt格式文件
  11. codeforces659B
  12. 【HDU 5363】Key Set(和为偶数的子集个数)
  13. Linux 命令详解(五)cp 命令两个高效的用法
  14. JAVA之字母与相对应数字转换
  15. npm install "Unexpected end of JSON input while parsing near"问题
  16. 近期js
  17. Java基础-IO流对象之内存操作流(ByteArrayOutputStream与ByteArrayInputStream)
  18. JDK源码分析(四)——LinkedHashMap
  19. Mybatis最入门---代码自动生成(generatorConfig.xml配置)
  20. Linux系统的环境变量$PATH

热门文章

  1. 动画requestAnimationFrame
  2. .NET 4.6.2正式发布带来众多特性
  3. bootstrap + requireJS+ director+ knockout + web API = 一个时髦的单页程序
  4. DDR的前世与今生(一)
  5. [Java 缓存] Java Cache之 DCache的简单应用.
  6. In-Memory:内存优化表 DMV
  7. 深究标准IO的缓存
  8. npm package.json属性详解
  9. PHP之时间和日期函数
  10. 编译器开发系列--Ocelot语言4.类型定义的检查