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

一、前言

  前几篇已经实现了一个最简单的购买过程,这次开始往这个过程中增加一些东西。比如促销、会员价等,在我们的第一篇文章(如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念)中规划的上下文映射图可以看到,这些都属于一个独立的上下文(售价上下文)。

二、如何在一个项目中实现多个上下文的业务

  一般情况下,为了更好的分而治之,把不同的上下文作为单独的service,然后通过rpc框架(如WCF)来对其访问是个比较常见的做法。但是在一些小型团队中,虽然划分出了不同上下文,但是我们的开发团队还是同一个。在这种情况下,我个人一般的做法是直接在同一个解决方案中建立不同的项目去做,但是这里需要在解决方案中明确的划分好不同上下文之间的边界,通过代码审核等手段管理好这个边界不被破坏。

                      【图1】

  增加的几个项目如图1所示。

三、售价上下文与购买上下文的集成

  根据我们第一篇如何一步一步用DDD设计一个电商网站(一)—— 先理解核心概念所定义的上下文映射图和9种集成模式可以看出,这2个上下文在同一个子域中,并且在我们实际业务场景中,这2者又是相辅相成,所以售价上下文和购买上下文是一种合作关系。确立这个关系之后,那么这个促销的计算逻辑到底是放到哪个上下文种做更合适呢?我们先整理一下几种可能的方式:

  1.购买上下文把购物车中的商品信息丢给售价上下文 --> 售价上下文进行计算 --> 把结果再返回给购买上下文。

  2.购买上下文从销价上下文获取相关会员价和促销信息 --> 再本地的购物车对象基础上进行运算,并直接可运用结果。

  3.再抽出一个专门的计算服务(隶属于售价上下文),去做这个计算的动作。购买上下文把购物车中的商品信息丢给计算服务 --> 计算上下文从销价上下文获取到相关会员价和促销信息 --> 计算 --> 返回结果给购买上下文

  我相信1和2是比较主流的2个方式。但是方式2是把售价上下文仅作为一种数据的提供方,这就把合作关系变成了一个上下游的关系,并且这种方式使得促销规则和购物车强耦合到了一起,不利于促销规则的变化。在这里售价上下文只起了一个简单的数据维护作用,无法完全控制“售价”的定义,没有很好的做到职责分离。方式1和3对购买上下文来说其实是没有区别的,只是方式3让整个数据交互的链路多了一层,会产生额外的开销,好处是服务的粒度更细了,需要结合实际情况权衡一下得失。这里我选择1方式来实现,因为我们在项目初期,还是尽可能的减少非业务目的的拆分导致的额外成本。

  好了,确定了集成方式之后,先把2个上下文之间用于数据交互的DTO模型定一下,如下图2(售价上下文的DTO模型),图3(购买上下文中与前者对应的值对象)。

【图2】

                  【图3】

  另外在图3中可以发现增加了一个ISellingPriceService,抽象了与售价上下文的交互。那么我们在Mall.Infrastructure.Translators项目中增加对这个上下文的防腐层处理,老3样SellingPriceAdapter(发起上下文数据请求的适配器)、SellingPriceService(实现ISellingPriceService)、SellingPriceTranslator(把远程数据对象转换成本地的值对象),代码很简单大家可以在源码中查看。需要注意的是,这里的Mall.Infrastructure.Translators项目仅增加了对Mall.Application.SellingPrice项目的引用,类似于把它当作一个远程资源来对待(按上面所说,如果实际由不同的团队负责可以物理上的分离到2个解决方案中)。

  最后创建一个CartService,里面的GetCart()方法——获取购物车信息,来作为调用发起方。这其中的实现使用了最简单的方式,本地不做任何的数据冗余,代码如下:

    public class CartService
    {
        private readonly static ConfirmUserCartExistedDomainService _confirmUserCartExistedDomainService = new ConfirmUserCartExistedDomainService();

        public CartDTO GetCart(string userId)
        {
            var cart = _confirmUserCartExistedDomainService.GetUserCart(userId);

            if (cart.IsEmpty())
            {
                return null;
            }

            var sellingPriceCart = DomainRegistry.SellingPriceService().Calculate(cart);
            return ConvertToCart(cart, sellingPriceCart);
        }

        private CartDTO ConvertToCart(Cart cart, SellingPriceCart sellingPriceCart)
        {
            return new CartDTO
            {
                CartItemGroups = sellingPriceCart.CalculatedFullGroups.Select(ent => new CartItemGroupDTO
                {
                    CartItems = ent.CalculatedCartItems.Select(e => ConvertToCartItem(e, cart.GetCartItem(e.ProductId))).ToArray(),
                    ReducePrice = ent.ReducePrice
                }).ToArray(),
                CartItems = sellingPriceCart.CalculatedCartItems.Select(ent => ConvertToCartItem(ent, cart.GetCartItem(ent.ProductId))).ToArray()
            };
        }

        private CartItemDTO ConvertToCartItem(SellingPriceCartItem sellingPriceCartItem, CartItem cartItem)
        {
            var product = DomainRegistry.ProductService().GetProduct(cartItem.ProductId);
            return new CartItemDTO
            {
                ProductId = cartItem.ProductId,
                ProductName = product == null ? "商品已失效" : product.SaleName,
                ReducePrice = sellingPriceCartItem.ReducePrice,
                SalePrice = cartItem.Price
            };
        }
    }    

 四、结语

  这次有个全局改动这里提一下,我在本次编码中把之前所有的Guid标识全部改为了string类型,弱化了对唯一标识的数据类型约束,提高可扩展性(如自增字段、其它自定义的唯一标识等),另外还把购物项中的Price改为了UnitPrice,让语义更加清晰。本篇内容比较粗,欢迎大家探讨。

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

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏空调论坛

空调自控系统全解析

(1) 空调区域的温度、湿度、压力等的控制,对于舒适空调,温湿度过高过低都影响舒适感,只有自控才能将温湿度自动控制在设计值;对于工艺空调,是生产工艺的必备条件。

1223
来自专栏安富莱嵌入式技术分享

【安富莱原创开源应用第1期】花式玩转网络摄像头之TCP上位机软件实现,高端大气上档次,速度2MB/S,华丽丽的界面效果

说明: 1、例子是两年前做的,一直没有顾上整理出来,今天特地整理出来,开源出来给大家玩。 2、上位机是emWin模拟器开发的,大家估计很难猜到,所以你会emWi...

1723
来自专栏吉浦迅科技

NVIDIA Jetson开发压箱底的秘密都在这里,很多人还不知道(一)

经常有人在群里问我各种“小”问题: Jetson TX2 显存是多大? Jetson TX2 开发板的尺寸是多大?给我个孔位图纸 Jetson TX2 支持最...

8038
来自专栏一个番茄说

iOS 开发安全那些事儿

​ 随着移动互联网的普及,被越来越多的心怀不轨的人觊觎,也越来越多的安全问题暴露了出来。开发者开发出来的应用被安装在设备上之后,用户并不具有专业的安全知识。...

1133
来自专栏Samego开发资源

那个程序员的Linux常用软件清单

1.2K5
来自专栏WeTest质量开放平台团队的专栏

安卓易学,爬坑不易——腾讯老司机的RecyclerView局部刷新爬坑之路

安卓开发者都知道,RecyclerView比ListView要灵活的多,但不可否认的里面的坑也同样埋了不少人。下面让我们看看腾讯开发工程师用实例讲解自己踩坑时的...

1112
来自专栏玄魂工作室

使用yo-get下载视频网站视频或其

前言 很多时候随时随地在线看视频不是那么容易,尤其是YouTube的视频(你懂得),所以遇到好的视频下载下来还是有必要的。今天给大家介绍就是一款下载视频站视频的...

6205
来自专栏MessageQueue

2017上海QCon之旅总结(下)

本来这个公众号的交流消息中间件相关的技术的。十月去上海参加了QCon,第一次参加这样的技术会议,感受挺多的,所以整理一下自己的一些想法接公众号和大家交流一下。

1132
来自专栏ThoughtWorks

在前端性能优化中应用HTTP缓存的三部曲|洞见

Spike先生是Best Experience公司的IT运营部门主管,他的团队成功地利用Http Cache优化了前端工程。 Spike将通过三个Scenari...

3294
来自专栏Java社区

十个Java实战开发中必备的小策略

为什么开发要用GIT呢?因为要给自己一颗后悔药吃。只要经常commit,文件就可以随时回退到某个时刻的内容,再也不担心别人改了自己的文件,自己误删了文件,特别是...

3257

扫码关注云+社区

领取腾讯云代金券