本系列所有文章

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

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

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

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

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

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

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

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

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

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

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

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

如何一步一步用DDD设计一个电商网站(十三)—— 领域事件扩展

阅读目录

  • 前言
  • 回顾
  • 本地的一致性
  • 领域事件发布出现异常
  • 订阅者处理出现异常
  • 结语

一、前言

  上篇中我们初步运用了领域事件,其中还有一些问题我们没有解决,所以实现是不健壮的,下面先来回顾一下。

二、回顾

  先贴一下上篇中的遗留的问题:

        public Result Create(OrderRequest orderRequest){if (!string.IsNullOrWhiteSpace(orderRequest.CouponId)){var couponResult = DomainRegistry.SellingPriceService().IsCouponCanUse(orderRequest.CouponId, orderRequest.OrderTime);if (!couponResult.IsSuccess)return Result.Fail(couponResult.Msg);}var orderId = DomainRegistry.OrderRepository().NextIdentity();var order = Domain.Order.Aggregate.Order.Create(orderId, orderRequest.UserId, orderRequest.Receiver,orderRequest.CountryId, orderRequest.CountryName, orderRequest.ProvinceId, orderRequest.ProvinceName,orderRequest.CityId, orderRequest.CityName, orderRequest.DistrictId, orderRequest.DistrictName,orderRequest.Address, orderRequest.Mobile, orderRequest.Phone, orderRequest.Email,orderRequest.PaymentMethodId, orderRequest.PaymentMethodName, orderRequest.ExpressId,orderRequest.ExpressName, orderRequest.Freight, orderRequest.CouponId, orderRequest.CouponName, orderRequest.CouponValue, orderRequest.OrderTime);foreach (var orderItemRequest in orderRequest.OrderItems){order.AddOrderItem(orderItemRequest.ProductId, orderItemRequest.Quantity, orderItemRequest.UnitPrice, orderItemRequest.JoinedMultiProductsPromotionId, orderItemRequest.ProductName);}DomainRegistry.OrderRepository().Save(order);DomainEventBus.Instance().Publish(new OrderCreated(order.ID, order.UserId, order.Receiver));return Result.Success();}

  不知道大家有没有发现这里代码上的一个问题,就是DomainEventBus.Instance().Publish()方法在聚合的Save操作之后进行,其实本身不是很符合DDD的概念,任何的领域事件都是基于一个领域对象的,没有领域对象何来领域事件,所以领域事件一般都是由领域对象内部产生,故这里应该要把DomainEventBus.Instance().Publish()方法搬到Order.Create中调用。如果发现这个问题的童鞋,恭喜你对于领域事件的理解已经又深入了一个层次了。好了上篇中这么写其实是为了凸显出本地数据修改提交和领域事件的发布是涉及到数据一致性的问题的,其中的问题是:

  1.如果领域事件发布出现异常了怎么办?

  2.如果订阅者处理出现异常了怎么办?

  本篇我们就来一个一个解决问题。

三、本地的一致性

  在解决上面的2个问题之前,我们先需要考虑在修改多个聚合的场景下本地上下文内的一致性问题,这个职责在DDD中由工作单元(UnitOfWork)来负责,工作单元就是为了保证本地的事务一致性,在.Net里的实现一般就是对SqlTransaction的封装运用。关于工作单元的实现一般有2种方式:

  (1)完全依赖于SqlTransaction,在工作单元第一次运用的时候就开启数据库事务。

  (2)使用本地变量存储变动的聚合,然后在工作单元Commit()的时候开启数据库事务并写入。

  2个实现方案各有优缺点,需要在一致性和性能之间做出权衡。另外工作单元和领域事件发布的结合运用可以参考我之前写的2篇文章:DDD设计中的Unitwork与DomainEvent如何相容?和DDD中的Unitwork与DomainEvent如何相容?(续),注意的是我在这2篇中运用的是方式(2)的实现方式。秉着没有最好只有更好的精神,如何才能做到更好的一致性,这里需要引出几个架构层面的概念:ES、Saga、A+ES。这些内容有一篇蟋蟀兄的文章(传送门在此)讲的很好,推荐大家阅读一下,我就不展开讲这些内容了。里面每一种方案的运用都有成本,大家根据实际情况权衡再运用即可,切记:软件开发中没有银弹。

四、领域事件发布出现异常

  这个现象是否会出现需要根据领域事件发布的实现方式来决定,只要实现方式是“非本地”的方案,那么必然会出现一些异常的状况。假如领域事件是通过消息队列来实现,那么涉及到了网络传输必然会大大的增加出现异常的可能性。如何来解决此类问题,秉承着一图胜千言的思想我直接贴个思维导图,先看下一般的几种实现方案的特点,见图1:

                             【图1】

  根据这个图,我们发现鱼和熊掌不可兼得,每个方案都由各自的特点,我们应当根据不同的场景使用不同的实现方案去做,才是最好的选择,并且据我所知,目前支持事务的消息队列开源方案非常的少,所以我们需要通过一定的补偿机制来处理与消息队列通信出现问题的场景。另外在分布式系统中,服务端的接口设计尽量需要满足无状态和幂等性(不展开去讲了,大家自行百度或者google),这也是整个系统高可用的重要的一环。最后的最后,通过对账机制作为最后一道防线,确保重要的数据不产生差错。

  那么我们来看一下这2个实现方案对应我们的编码应该如何来做:

  1.通过消息机制的发布就是把我在Demo中运用DomainEventBus的内部实现由Dictionary替换为外部的消息队列即可,然后需要注册DistributeExceptionEvent来处理丢给消息队列进行分发时出现异常的问题,做补偿措施。

  2.通过DB的方案,大致的伪代码如下:

            var unitOfWork = new UnitOfWork();unitOfWork.RegisterSaved(order);var domainEvents = GetEventsFromBus();foreach(var domainEvent in domainEvents){var body = Serialize(domainEvent);unitOfWork.RegisterSaved(new Message{Body = body});}return unitOfWork.Commit();

  大家可以看到,这个方式首先带来的问题是让工作单元变得异常的臃肿,随之导致整个事务的总耗时增加。并且此时Message表中的现存数据可能还在同步进行消费/推送,那么产生资源竞争是必然会遇到的问题,导致的后果是整个工作单元的提交失败。

五、订阅者处理出现异常

  这个问题也是比较常见的,特别是处理业务复杂的接口和涉及过多RPC调用的接口出现的概率更大。所以每个应用每个接口都需要考虑好此类问题。一般的解决方案我也梳理了一个思维导图,如下图2:

                              【图2】

  其实很明显通过回滚的方式有很多局限性。所以说个人建议选择下面的方案,尽量做到内部消化,以提高接口对外的自治性。另外针对重试进行一些限制,一是为了减少一些无用功来占用系统资源,二是避免在系统本身达到瓶颈的情况下出现马太效应,让拥堵问题越发严重。

六、结语

  本篇没有增加太多代码,只是在Mall.Infrastructure中增加了几个工作单元(方式(2))相关的类,其中只包含了一些核心逻辑代码,具体的实现希望大家能够自己动手。多谢各位看官。

本文完整的源码地址:https://github.com/ZacharyFan/DDDDemo/tree/Demo13。

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

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

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

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

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

转载于:https://www.cnblogs.com/Zachary-Fan/p/DDD_13.html

如何一步一步用DDD设计一个电商网站(十三)—— 领域事件扩展相关推荐

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

    本系列所有文章 如何一步一步用DDD设计一个电商网站(一)-- 先理解核心概念 如何一步一步用DDD设计一个电商网站(二)-- 项目架构 如何一步一步用DDD设计一个电商网站(三)-- 初涉核心域 如 ...

  2. 系统设计题:如何设计一个电商平台积分兑换系统!

    本文来自公众号:狸猫技术窝 作者:原子弹大侠,阿里高级技术专家 1.拉开差距的一类面试题 现在面试经常会遇到一类问题,面试官让你现场设计出某个业务场景下的一个系统,这个系统往往在业务或者技术上有一定难 ...

  3. html期末作业代码网页设计——化妆品电商网站(4页) HTML+CSS+JavaScript 使用html+css实现一个静态页面(含源码)

    HTML5期末大作业:化妆品电商网站设计--化妆品电商网站(4页) HTML+CSS+JavaScript 使用html+css实现一个静态页面(含源码) 常见网页设计作业题材有 个人. 美食. 公司 ...

  4. HTML5期末大作业:化妆品电商网站设计——化妆品电商网站(4页) HTML+CSS+JavaScript 使用html+css实现一个静态页面(含源码)

    HTML5期末大作业:化妆品电商网站设计--化妆品电商网站(4页) HTML+CSS+JavaScript 使用html+css实现一个静态页面(含源码) 常见网页设计作业题材有 个人. 美食. 公司 ...

  5. 人气流量通通要!电商网站商品页设计超全面指南

    编者按:今天这篇文章读完后,同学们可以来做一个小练习,根据文章,把优设看做一个电商网站,文章看做商品,来分析一下哪篇文章是热门,有哪些突出的方法,文章里藏有哪些引导用户的细节等等,即学即用,不容易忘, ...

  6. 设计一个成功的网站的黄金五步

              想要设计一个成功的网站,需要注意一下五点:         一.定位网站的主题和名称 网站的主题也就是网站的题材,网站设计开始首先遇到的问题.网站题材千奇百怪,琳琅满目,只要想的到 ...

  7. 做电商网站php开发的流程,如何开发电商平台-电商开发第一步

    快速开发电商平台,创建一个企业电商网站,同时获得盈利的巨额收益要怎么做呢?但是你如何创建电子商务网站呢?易龙天电商网站开发工程师及七星迪曼整合营销专家为您解答,通过9个步骤创建企业级电子商务网站. 构 ...

  8. 设计电商网站必看,如何改善用户体验

    电子商务是一个复杂的系统,很多小伙伴在设计电商网站的时候会遇到不少的问题.作为电子商务的典型模式之一,B2C网上零售网站要考虑的因素很多,有时候很多意想不到的因素都可能决定一个用户最终是否在该网站完成 ...

  9. 如何设计一个成功的网站

    随着网络技术的不断发展,网络应用已经渗透到人类社会的各个角落.作为网络世界的支撑点的网站,更是人们关注的热点:政府利用网站宣传自己的施政纲领,日益成为与百姓交流的直通车:企业利用网站宣传自己的形象,挖 ...

最新文章

  1. Pydev 找不到新安装的 Python egg 解决方法
  2. python 对象_python面向对象
  3. 【shell】 初次接触shell编程,记录一下遇到的问题
  4. 计算机access2级选择题真题,计算机二级《ACCESS》选择题练习及答案
  5. Eclipse插件中的SLF4J登录
  6. ad17编辑界面怎么检查未连线_软件账务处理流程之——凭证审核与检查
  7. 那些年我们用过神级的代码注释
  8. python中self做前缀_python 创建类和为什么类方法中self形参必不可少?
  9. CPU指令系统寻址方式
  10. 《Unity 4 3D开发实战详解》一6.3 粒子系统
  11. 在 windows 下安装 Boost 1.62.0
  12. 关于移植人脸识别功能到linux开发板
  13. ListView 上下拉刷新
  14. 判断图有无环_数读湾区经济潜能:基于大数据分析的环杭州湾大湾区“一体化”发展潜能!...
  15. python读取文件夹下所有txt_Python读取文件夹中TXT文档
  16. i5 12400f和i5 12600kf参数对比
  17. 最全Python算法入门
  18. jsp网页无法加载css解决方法
  19. 使用 Jquery AjaxUpload 上传图片
  20. vbs实现的支持拖动的txt文本切割器

热门文章

  1. 在 Unity 中基于 Oculus DK1 的开发
  2. 使用代码创建AutoLayout约束
  3. 刨根问底Objective-C Runtime(2)- Object Class Meta Class
  4. 学习日记-类继承中的上下转换
  5. 构建通用类型- 继承 VS 聚合
  6. sqlite3命令详解
  7. 操作 Docker 容器
  8. s6-1 传输层概述
  9. c#web页面显示弹窗_C#中三种弹出信息窗口的方式
  10. 双12来了!揭秘秒杀剁手背后的云数据库PolarDB!