设为首页 加入收藏

TOP

如何一步一步用DDD设计一个电商网站(四)—— 把商品卖给用户(二)
2017-10-13 10:41:50 】 浏览:8230
Tags:如何 步一步 DDD 设计 一个 电商 网站 商品 用户
)
", "productId"); if (quantity <= 0) throw new ArgumentException("quantity不能小于等于0", "quantity"); if (quantity < 0) throw new ArgumentException("price不能小于0", "price"); this.ProductId = productId; this.Quantity = quantity; this.Price = price; } public void ModifyQuantity(int quantity) { this.Quantity = quantity; } }

 

  回到我们最上面的代码中的“user.Buy(product, quantity);” 的问题。在DDD中主张的是清晰的业务边界,在这里,我们目前的定义导致的结果是User与Cart产生了强依赖,让User内部需要知道过多的Cart的细节,而这些是User不应该知道的。这里还有一个问题是在领域对象内部去访问仓储(或者调用远程上下文的接口)来获取数据并不是一种提倡的方式,他会导致事务管理的混乱。当然有人会说,把Cart作为一个参数传进来,这看上去是个好主意,解决了在领域对象内部访问仓储的问题,然而看一下接口的定义,用户购买商品和购物车?还是用户购买商品并且放入到购物车?这样来看这个方法做的事情似乎过多了,违背了单一职责原则。

  其实在大部分语义中使用“用户”作为一个主体对象,看上去也都还挺合理的,然而细细的去思考当前上下文(系统)的核心价值,会发现“用户”有时并不是核心,当然比如是一个CRM系统的话核心即是“用户”。

  总结一下这种方式的缺点:

  A.领域对象之间的耦合过高,项目中的对象容易形成蜘蛛网结构的引用关系。

  B.需要在领域对象内部调用仓储,不利于最小化事务管理。

  C.无法清晰的表达出通用语言的概念。

  重新思考这个方法。“购买”这个概念更合理的描述是在销售过程中所发生的一个操作过程。在我们电商行业下,可以表述为“用户购买了商品”和“商品被加入购物车”。这时候需要领域服务出场了,由它来表达出“用户购买商品”这个概念最为合适不过了。其实就是把应用层的代码搬过来了,以下是对应的代码: 

 

    public class UserBuyProductDomainService
    {
        public CartItem UserBuyProduct(Guid userId, Guid productId, int quantity)
        {
            var user = DomainRegistry.UserService().GetUser(userId);
            if (user == null)
            {
                throw new ApplicationException("未能获取用户信息!");
            }

            var product = DomainRegistry.ProductService().GetProduct(productId);
            if (product == null)
            {
                throw new ApplicationException("未能获取产品信息!");
            }

            return new CartItem(productId, quantity, product.SalePrice);
        }
    }

三、领域服务的使用

  领域中的服务表示一个无状态的操作,它用于实现特定于某个领域的任务。当某个操作不适合放在聚合和值对象上时,最好的方式便是使用领域服务了。

1.列举几个领域服务适用场景

    A.执行一个显著的业务操作过程。

    B.对领域对象进行转换。

    C.以多个领域对象作为输入进行计算,结果产生一个值对象。

  D.隐藏技术细节,如持久化与缓存之间的依存关系。

2.不要把领域服务作为“银弹”。过多的非必要的领域服务会使项目从面向对象变成面向过程,导致贫血模型的产生。

3.可以不给领域服务创建接口,如果需要创建则需要放到相关聚合、实体、值对象的同一个包(文件夹)中。服务的实现可以不仅限于存在单个项目中。

 

四、回到现实

  按照这样设计之后我们的应用层代码变为:

 

1             var cartItem = _userBuyProductDomainService.UserBuyProduct(userId, productId, quantity);
2             var cart = DomainRegistry.CartRepository().GetOfUserId(userId);
3             if (cart == null)
4             {
5                 cart = new Cart(DomainRegistry.CartRepository().NextIdentity(), userId, DateTime.Now);
6             }
7             cart.AddCartItem(cartItem);
8             DomainRegistry.CartRepository().Save(cart);    

 

  这里的第5行用到了一个仓储(资源库)CartRepository,仓储算是DDD中比较好理解的概念。在DDD中仓储的基本思想是用面向集合的方式来体现,也就是相当于你在和一个List做操作,所以切记不能把任何的业务信息泄露到仓储层去,它仅用于数据的存储。仓储的普遍使用方式如下:

  A.包含保存、删除、指定条件的查询(当然在大型项目中可以考虑采用CQSR来做,把查询和数据操作分离)。

  B.只为聚合创建资源库

  C.通常资源库与聚合式 1对1的关系,然而有时,当2个或者多个聚合位于同一个对象层级中时,它们可以共享同一个资源库。 

  D.资源库的接口定义和聚合放在相同的模块中,实现类放在另外的包中(为了隐藏对象存储的细节)。

  回到代码中来,标红的那部分也可以用一个领域服务来实现,隐藏“如果一个用户没有购物车的情况下新建一个购物车”的业务细节。

 

    public class GetUserCartDomainService
    {
        public Cart GetUserCart(Guid userId)
        {
            var cart = DomainRegistry.CartRepository().GetOfUserId(userId);
            if (cart == null)
            {
                cart = new Cart(DomainRegistry.CartRepository().NextIdentity(), userId, DateTime.Now);
                DomainRegistry.CartRepository().Save(cart);
            }

            return cart;
        }
    }

  这样应用层就真正变成了一个讲故事的人,清晰的表达出了“用户购买商品的整个过程”,把商品购物车的商品转换成购物车明细 --> 获取用户的购物车 --> 添加购物车

首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Spring(三)__aop编程 下一篇设计模式(十一):迭代器模式

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目