如何一步一步用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)。坚持用心打磨每一篇高质量原创。欢迎扫描右侧的二维码~。
定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。
如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。
如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。
如何一步一步用DDD设计一个电商网站(五)—— 停下脚步,重新出发的更多相关文章
- 如何一步一步用DDD设计一个电商网站(九)—— 小心陷入值对象持久化的坑
阅读目录 前言 场景1的思考 场景2的思考 避坑方式 实践 结语 一.前言 在上一篇中(如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成),有一行注释的代码: public interfa ...
- 如何一步一步用DDD设计一个电商网站(八)—— 会员价的集成
阅读目录 前言 建模 实现 结语 一.前言 前面几篇已经实现了一个基本的购买+售价计算的过程,这次再让售价丰满一些,增加一个会员价的概念.会员价在现在的主流电商中,是一个不大常见的模式,其带来的问题是 ...
- 如何一步一步用DDD设计一个电商网站(十)—— 一个完整的购物车
阅读目录 前言 回顾 梳理 实现 结语 一.前言 之前的文章中已经涉及到了购买商品加入购物车,购物车内购物项的金额计算等功能.本篇准备把剩下的购物车的基本概念一次处理完. 二.回顾 在动手之前我对之 ...
- 如何一步一步用DDD设计一个电商网站(七)—— 实现售价上下文
阅读目录 前言 明确业务细节 建模 实现 结语 一.前言 上一篇我们已经确立的购买上下文和销售上下文的交互方式,传送门在此:http://www.cnblogs.com/Zachary-Fan/p/D ...
- 如何一步一步用DDD设计一个电商网站(六)—— 给购物车加点料,集成售价上下文
阅读目录 前言 如何在一个项目中实现多个上下文的业务 售价上下文与购买上下文的集成 结语 一.前言 前几篇已经实现了一个最简单的购买过程,这次开始往这个过程中增加一些东西.比如促销.会员价等,在我们的 ...
- 如何一步一步用DDD设计一个电商网站(四)—— 把商品卖给用户
阅读目录 前言 怎么卖 领域服务的使用 回到现实 结语 一.前言 上篇中我们讲述了“把商品卖给用户”中的商品和用户的初步设计.现在把剩余的“卖”这个动作给做了.这里提醒一下,正常情况下,我们的每一步业 ...
- 如何一步一步用DDD设计一个电商网站(三)—— 初涉核心域
一.前言 结合我们本次系列的第一篇博文中提到的上下文映射图(传送门:如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念),得知我们这个电商网站的核心域就是销售子域.因为电子商务是以信息网络 ...
- 如何一步一步用DDD设计一个电商网站(十一)—— 最后的准备
阅读目录 前言 准备 实现 结语 一.前言 最近实在太忙,上周停更了一周.按流程一步一步走到现在,到达了整个下单流程的最后一公里——结算页的处理.从整个流程来看,这里需要用户填写的信息是最多的,那么 ...
- 如何一步一步用DDD设计一个电商网站(十二)—— 提交并生成订单
阅读目录 前言 解决数据一致性的方案 回到DDD 设计 实现 结语 一.前言 之前的十一篇把用户购买商品并提交订单整个流程上的中间环节都过了一遍.现在来到了这最后一个环节,提交订单.单从业务上看,这个 ...
随机推荐
- 懒加载session 无法打开 no session or session was closed 解决办法(完美解决)
首先说明一下,hibernate的延迟加载特性(lazy).所谓的延迟加载就是当真正需要查询数据时才执行数据加载操作.因为hibernate当中支持实体对象,外键会与实体对象关联起来.如 ...
- 利用CSS中的:after、: before制作的边三角提示框
小颖昨天分享了一篇参考bootstrap中的popover.js的css画消息弹框今天给大家再分享一篇使用:before和:after伪元素画消息弹框的CSS. 画出来是介个酱紫的: 有没有觉得画的萌 ...
- geotrellis使用(二十八)栅格数据色彩渲染(多波段真彩色)
目录 前言 实现过程 总结 一.前言 上一篇文章介绍了如何使用Geotrellis渲染单波段的栅格数据,已然很是头疼,这几天不懈努力之后工作又进了一步,整清楚了如何使用Geotrelli ...
- Entity Framework的启动速度优化
最近开发的服务放到IIS上寄宿之后,遇到一些现象,比如刚部署之后,第一次启动很慢:程序放置一会儿,再次请求也会比较慢.比如第一个问题,可以解释为初次请求某一个服务的时候,需要把程序集加载到内存中可能比 ...
- PHP static静态属性和静态方法
这里分析了php面向对象中static静态属性和静态方法的调用.关于它们的调用(能不能调用,怎么样调用),需要弄明白了他们在内存中存放位置,这样就非常容易理解了.静态属性.方法(包括静态与非静态)在内 ...
- vim环境变量配置、背景色配置
我们使用vi或者vim的时候,如果想要显示行号,可能会这样做:切换到命令模式,然后输入set nu,再按回车键就显示了:还有就是咱们在编写程序的时候,有的时候会希望按下回车键后,光标不是每次都在行首, ...
- 《CPU的工作过程》
本文转载自inter官方网址:https://software.intel.com/zh-cn/articles/book-Processor-Architecture_CPU_work_proces ...
- Unity C#最佳实践(上)
本文为<effective c#>的读书笔记,此书类似于大名鼎鼎的<effective c++>,是入门后提高水平的进阶读物,此书提出了50个改进c#代码的原则,但是由于主要针 ...
- java中判断字符串是否为只包含数字
1.用Java自带的函数 public static boolean isNumeric(String str){ for(int i=str.length();--i>=0;){ if (!C ...
- CYQ.Data V5 分布式缓存Redis应用开发及实现算法原理介绍
前言: 自从CYQ.Data框架出了数据库读写分离.分布式缓存MemCache.自动缓存等大功能之后,就进入了频繁的细节打磨优化阶段. 从以下的更新列表就可以看出来了,3个月更新了100条次功能: 3 ...