万字长文!多图!结合DDD讲清楚编写技术方案的七大维度( 三 )


业界流传着一句话:一图胜千言,其中一个重要原因是文字是线性的,所以表达逻辑分支能力不如流程视图,而在流程视图中表达逻辑分支能力最强正是活动图 。

万字长文!多图!结合DDD讲清楚编写技术方案的七大维度

文章插图
 
 
3.3.2 顺序图顺序图侧重于交互,适合按照时间顺序体现一个业务流程中交互细节,但是顺序图并不擅长体现复杂逻辑分支 。
如果某个逻辑分支特别重要,可以选择再画一个顺序图 。例如支付流程中有支付成功正常流程,也有支付失败异常流程,这两个流程都非常重要,所以可以用两张顺序图体现 。回到本文实例,我们可以通过顺序图体现球员从提出转会到比赛全流程 。
万字长文!多图!结合DDD讲清楚编写技术方案的七大维度

文章插图
 
 
3.3.3 状态机图假设一条数据有ABC三种状态,从正常业务角度来看,状态只能从A流转到B,再从B流转到C,不能乱序也不可逆 。但是可能出现这种异常情况:数据当前状态为A,接收异步消息更改状态,B消息由于延时晚于C消息,最终导致状态先改为C再改为B,那么此时状态就是错误的 。
状态机图侧重于状态流转,说明了哪些状态之间可以相互流转,在实际开发中再结合状态机代码模式,可以解决上述状态异常情况 。回到本文实例,我们可以通过状态机图表示球员从提出转会到签约整个状态流程 。
万字长文!多图!结合DDD讲清楚编写技术方案的七大维度

文章插图
 
 
3.4 领域与数据上述章节从功能层面和流程层面进行了系统分析,现在从数据层分析系统,我们首先对比两组概念:值对象与实体,领域对象与数据对象 。
实体是具有唯一标识的对象,唯一标识会伴随实体对象整个生命周期并且不可变更 。值对象本质上是属性的集合,没有唯一标识 。
【万字长文!多图!结合DDD讲清楚编写技术方案的七大维度】领域对象与数据对象一个重要的区别是值对象存储方式 。领域对象在包含值对象的同时也保留了值对象的业务含义,而数据对象可以使用更加松散的结构保存值对象,简化数据库设计 。
现在我们需要管理足球运动员基本信息和比赛数据,对应领域模型和数据模型应该如何设计?姓名、身高、体重是一名运动员本质属性,加上唯一编号可以对应实体对象 。跑动距离,传球成功率,进球数是运动员比赛表现,这些属性的集合可以对应值对象 。
万字长文!多图!结合DDD讲清楚编写技术方案的七大维度

文章插图
 
我们根据图示编写领域对象与数据对象代码:
// 数据对象public class FootballPlayerDO {private Long id;private String name;private Integer height;private Integer weight;private String gamePerformance;}// 领域对象public class FootballPlayerDMO {private Long id;private String name;private Integer height;private Integer weight;private GamePerformanceVO gamePerformanceVO;}public class GamePerformanceVO {private Double runDistance;private Double passSuccess;private Integer scoreNum;}为什么要采用JSON存储值对象?因为脚本化是一种拓展灵活性的方式,脚本化不仅指使用groovy、QLExpress脚本增强系统灵活性,还包括松散可扩展的数据结构 。数据模型抽象出了姓名、身高、体重这些基本属性,对于频繁变化的比赛表现属性,这些属性值可能经常变化,甚至属性本身也是经常变化,可能会加上射门次数,突破次数等,所以采用松散结构进行存储 。
如果需要根据JSON结构中KEY进行检索,例如查询进球数大于5的球员,这也不是没有办法 。我们可以将MySQL表中数据平铺到ES中,一条数据根据JSON KEY平铺变成为多条数据,这样就可以进行检索了 。
 
3.5 纵横做设计复杂业务之所以复杂,一个重要原因是涉及角色或者类型较多,很难平铺直叙地进行设计,所以我们需要增加分析维度 。其中最常见的是增加横向和纵向两个维度,本文也着重讨论两个维度 。总体而言横向扩展的是思考广度,纵向扩展的是思考深度,对应到系统设计而言可以总结为:纵向做隔离,横向做编排 。
我们首先分析一个下单场景 。当前有ABC三种订单类型:A订单价格9折,物流最大重量不能超过9公斤,不支持退款 。B订单价格8折,物流最大重量不能超过8公斤,支持退款 。C订单价格7折,物流最大重量不能超过7公斤,支持退款 。按照需求字面含义平铺直叙地写代码也并不难:
public class OrderServiceImpl implements OrderService {@Resourceprivate OrderMApper orderMapper;@Overridepublic void createOrder(OrderBO orderBO) {if (null == orderBO) {throw new RuntimeException("参数异常");}if (OrderTypeEnum.isNotValid(orderBO.getType())) {throw new RuntimeException("参数异常");}// A类型订单if (OrderTypeEnum.A_TYPE.getCode().equals(orderBO.getType())) {orderBO.setPrice(orderBO.getPrice() * 0.9);if (orderBO.getWeight() > 9) {throw new RuntimeException("超过物流最大重量");}orderBO.setRefundSupport(Boolean.FALSE);}// B类型订单else if (OrderTypeEnum.B_TYPE.getCode().equals(orderBO.getType())) {orderBO.setPrice(orderBO.getPrice() * 0.8);if (orderBO.getWeight() > 8) {throw new RuntimeException("超过物流最大重量");}orderBO.setRefundSupport(Boolean.TRUE);}// C类型订单else if (OrderTypeEnum.C_TYPE.getCode().equals(orderBO.getType())) {orderBO.setPrice(orderBO.getPrice() * 0.7);if (orderBO.getWeight() > 7) {throw new RuntimeException("超过物流最大重量");}orderBO.setRefundSupport(Boolean.TRUE);}// 保存数据OrderDO orderDO = new OrderDO();BeanUtils.copyProperties(orderBO, orderDO);orderMapper.insert(orderDO);}}


推荐阅读