文档章节

由单元测试发现的设计问题

drv
 drv
发布于 2016/12/28 11:18
字数 1152
阅读 11
收藏 0

  问题背景:采用DDD设计开发的公司内部业务审批系统,架构分层大概上就是经典DDD的架构,其中应用层使用AutoMapper对聚合和DTO做映射,聚合的大致模型:

  

  问题定位:问题发生在应用层,通过映射产生审批单聚合时,审批单聚合中最下层(如上图中业务关系明细)用于与其上层对象(如上图中业务信息)关系的唯一标识与预期不符,依旧是命名因为我的偏执,会有一点问题,知道就成了。

public AllographRequisitionData Save(AllographRequisitionData requisitionData)
        {
            DataObjectConvert convert = new DataObjectConvert();
            AllographRequisition requisition = convert.DTOToEntity(requisitionData);
            RequisitionService requisitionService = new RequisitionService();
            requisitionService.Create(requisition);
            return requisitionData;
        }

  上面代码就是单元测试未通过的代码,方法本返回值本应该是执行是否成功,不过为了检查问题,暂时放了DTO还没改回来,未通过检查的是Requisition聚合实例requisition,其中DTOToEntity()方法是适用AutoMapper进行映射的方法。

   问题原因: 聚合内部关系通过ID实现,由于最初考虑聚合模型比较简单,完全可以由聚合内部维护组装逻辑---即聚合内部实体的关系。但是应用层将聚合实体与DTO做映射时,聚合自身创建的ID被测试用例中DTO携带的ID覆盖了,这样额外产生了聚合内部一致性被应用层侵入的问题。

  问题解决有两种办法。一种是打补丁似的哪疼补哪,这种方法很简单,通常大多数程序员因为受限于经验可能发现不了或懒不愿意分析问题更深层次的原因,会选择这样的方法,只要在映射时忽略了ID就可以了,只要在映射时加上一句代码就可以解决了,这种方法隐式的使业务和映射产生了耦合。

.ForMember(target => target.RequisitionContentID, opt => opt.Ignore())

  好了,以上只是引子,我要说的其实是这个问题本就不应该存在。因为在查找这个问题的过程中,我调试了上面贴的Save方法单元测试中的两个部分:一个是聚合根构造函数产生聚合内部各对象间关系时,也就是产生ID,并分发给聚合内各个实体实例的部分;另外一个是自然是DTO到聚合映射的部分。

  但是很明显,我明明是一个聚合内维护内部关系的功能产生了问题,却同时必须关心另外一个本应毫不相干的地方,这已经说明了问题;而且映射方法的职责只是映射,不应该有额外对聚合逻辑关系的维护的职责。

  显然,由聚合跟来负责组装聚合,维护连接聚合内各部分的关系在这里不太合适,那就只好不由聚合来维护了。DDD中创建聚合有两种方式,另外一种是工厂,其实DTO映射出来的结果从概念上来所仅仅是聚合应当持有的数据,而并不是真正的完整的聚合,所以虽然Requisition实例已经产生,但聚合并没有完全形成。工厂一般有两个存在位置,一个是聚合根,一个是领域服务。这里为了工厂之后可以被仓储的重建过程调用,将工厂放在领域服务中。

  下面代码的return就当没看见吧,暂时没起作用。

public bool Create<TRequisition, TRequisitionContent, TBusinessContent>(RequisitionBase requisition)
            where TRequisition : RequisitionBase
            where TRequisitionContent : RequisitionContentBase
            where TBusinessContent : BusinessContentBase
        {
            bool isNotNull;
            ValidateHelper.IsNotNull(requisition, out isNotNull);
            RequisitionFactory build = new RequisitionFactory();
            build.BuildRequisitionID(requisition);
            RequistionRepository repository = new RequistionRepository();
            repository.Add<TRequisition, TRequisitionContent, TBusinessContent>(requisition as TRequisition);

            return true;
        }

  如是,就可以避免出现上面的问题,虽然说做设计总不可能方方面面、上上下下(不知道为什么想到了日日夜夜)、从头到尾全部一次性考虑完善,修改、重构、迭代一定是会存在的,但我很少写项目的代码,不知道以往的设计中这种程序员难以注意但有很大隐患的设计问题会存在多少,所以以此为警醒,一定要对设计做回归测试,有条件的情况下可以配合程序员进行开发,然后就是,认真、仔细、对所有设计部分尽量考虑清楚、对扩展和可能的使用方式进行估计,有条件可以做些测试。

© 著作权归作者所有

drv

drv

粉丝 2
博文 57
码字总数 75382
作品 0
东城
架构师
私信 提问
android单元测试好处

许多开发者都有个习惯,常常不乐意去写个简单的单元测试程序来验证自己的代码。对自己的程序一直非常有自信,或存在侥幸心理每次运行通过后就直接扔给测试组测试了。然而每次测试组的BUG提交...

席道坤
2016/12/05
16
0
C/C++单元测试问答(摘要)

为什么要进行单元测试? 单元测试保证局部代码的质量 单元测试改良项目代码的整体结构 单元测试降低测试、维护升级的成本 单元测试使开发过程适应频繁变化的需求 单元测试有助于提升程序员的能...

EasyTDD
2014/04/24
0
0
一行代码引发的CI悲剧

“墨菲定律”这样说:“凡事只要有可能出错,那就一定会出错。” 1 问题描述 周五时候,升级通信框架的剥离后,CI主机运行缓慢。增量编译情况下,整个整个流程运行26分钟,以前正常的情况为7...

通爸
2018/03/09
0
0
C/C++单元测试问答(摘要)

为什么要进行单元测试? 单元测试保证局部代码的质量 单元测试改良项目代码的整体结构 单元测试降低测试、维护升级的成本 单元测试使开发过程适应频繁变化的需求 单元测试有助于提升程序员的能...

dellfox
2014/04/24
0
0
关于单元测试,我们需要知道什么?- 引言篇

关于单元测试,我们需要知道什么?- 引言篇 前言 趁着刚读完《认知天性》这书,书有一点这样说:「我们学习行为更多凭着直觉,即使我们已经看到了科学数据,但我们也不愿意去相信自己的直觉存...

白霁
07/04
0
0

没有更多内容

加载失败,请刷新页面

加载更多

C++的变量初始化

C++中变量的初始化有很多种方式,如:默认初始化,值初始化,直接初始化,拷贝初始化,列表初始化。 1、默认初始化:默认初始化是指定义变量时没有指定初值时进行的初始化操作。 如:int a;...

天王盖地虎626
29分钟前
0
0
MySQL-入门(一)

一、SQL的分类 DDL(Data Definition Language):数据定义语言,用来操作数据库对象:库、表、列等; DML(Data Manipulation Language):数据操作语言,用来增删改数据库中的数据; DCL(...

潜行-L
32分钟前
1
0
微服务架构在Kubernetes上的实现

我们讨论了最近的微服务趋势,以及伴随微服务架构可能出现的一些复杂问题。在接下来的几周内,我们将深入探讨这个问题。我们将探讨不同设计选择中固有的权衡,以及可以采取哪些措施来缓解这些...

别打我会飞
33分钟前
3
0
IDEA2018导入eclipse web项目

看别人的攻略:https://blog.csdn.net/qq_33229669/article/details/83751316 完成之后,出现了IDEA编译java报错:找不到符号_的解决方法错误, 然后百度出来是编码问题, 1.更改editor的文件编码...

流光韶逝
48分钟前
3
0
NIO 学习

比如 socket 通信, 服务的的 socket 对应的 线程会一直等待 client 端的 消息。 这就是bio的 阻塞 。而且在高并发下 很容易出现问题。 1, 非阻塞式IO模型、 2. 弹性伸缩能力强(服务的的接...

之渊
51分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部