一、前言
上篇中我们讲述了“把商品卖给用户”中的商品和用户的初步设计。现在把剩余的“卖”这个动作给做了。这里提醒一下,正常情况下,我们的每一步业务设计都需要和领域专家进行沟通,尽可能的符合通用语言的表述。这里的领域专家包括但不限于当前开发团队中对这块业务最了解的开发人员、系统实际的使用人等。
二、怎么卖
如果在没有结合当前上下文的情况下,用通用语言来表述,我们很容易把代码写成下面的这个样子(其中DomainRegistry只是一个简单的工厂,解耦应用层与其他具体实现的依赖,内部也可以使用IOC容器来实现):
var user = DomainRegistry.UserService().GetUser(userId);
if (user == null)
{
return Result.Fail("未找到用户信息");
}
var product = DomainRegistry.ProductService().GetProduct(productId);
if (product == null)
{
return Result.Fail("未找到产品信息");
}
user.Buy(product, quantity);
return null;
初步来看,好像很合理。这里表达出的是“用户购买了商品”这个语义。然后继续往下写,我们会发现购买了之后应该怎么办呢,要把东西放到购物车啊。这里又出现了购物车,我认为购物车是我们销售子域中的一个核心概念,它也是整个用户购买过程中变化最频繁的一个对象。我们来梳理一下,一个最简单的购物车至少包含哪些东西:
A.一个购物车必须是属于一个用户的。
B.一个购物车内必然包含购买的商品的相关信息。
首先我们思考一下如何在我们的购物车中表达出用户的概念,购物车需要知道用户的所有信息吗?答案在大部分场景下应该是否定的,因为在用户挑选商品并加到购物车的这个过程中,整个购物车是不稳定的,那么其实在用户想要进行结算以前,我们只需要知道这个购物车是谁的,仅此而已。那么这里我们已经排除了一种方式是购物车直接持有User的引用。所以说对于购物车来说,在我们排除为性能而进行数据冗余的情况下,我们只需要保持一个用户唯一标识的引用即可。
购物车明细和商品之间的关系也是一样,每次需要从远程上下中获取到最新的商品信息(如价格等),故也仅需保持一个唯一标识的引用。
结合上一篇讲的,我们目前已经出现了以下几个对象,见【图1,点击图片查看原图】。
【图1】
下面贴上购物车和购物车明细的简单实现。
public class Cart : Infrastructure.DomainCore.Aggregate
{
private readonly List<CartItem> _cartItems;
public Guid CartId { get; private set; }
public Guid UserId { get; private set; }
public DateTime LastChangeTime { get; private set; }
public Cart(Guid cartId, Guid userId, DateTime lastChangeTime)
{
if (cartId == default(Guid))
throw new ArgumentException("cartId 不能为default(Guid)", "cartId");
if (userId == default(Guid))
throw new ArgumentException("userId 不能为default(Guid)", "userId");
if (lastChangeTime == default(DateTime))
throw new ArgumentException("lastChangeTime 不能为default(DateTime)", "lastChangeTime");
this.CartId = cartId;
this.UserId = userId;
this.LastChangeTime = lastChangeTime;
this._cartItems = new List<CartItem>();
}
public void AddCartItem(CartItem cartItem)
{
var existedCartItem = this._cartItems.FirstOrDefault(ent => ent.ProductId == cartItem.ProductId);
if (existedCartItem == null)
{
this._cartItems.Add(cartItem);
}
else
{
existedCartItem.ModifyQuantity(existedCartItem.Quantity + cartItem.Quantity);
}
}
}
public class CartItem : Infrastructure.DomainCore.Entity
{
public Guid ProductId { get; private set; }
public int Quantity { get; private set; }
public decimal Price { get; private set; }
public CartItem(Guid productId, int quantity, decimal price)
{
if (productId == default(Guid))
throw new ArgumentException("productId 不能为default(Guid