cover 在今年早些时候买了两本书,一本是O'Reilly的《微服务设计》,还有一本是《聊聊'架构'》, 《聊聊'架构'》 这本书之前是在机房借阅的时候看过的,觉得非常的不错,所以买来一本放在自己的桌边,有时间的时候看看,都会有一些收获。由于公司和社团都在做微服务相关方面的探索,所以买了一本动物书的《微服务设计》, 在读书的前几章的时候,几个熟悉的词又出现在了书中--"边界","上下文","生命周期"。这些在《聊聊'架构'》中作为贯穿的整本书的内容的词再一次出现在了我的视野中,然后动物书中还顺带提到了一本书《领域驱动开发》,而且很巧的是,学长也在竭力推荐这本书。所以买了一本书,断断续续,一直到年末的时候才基本看完。

其实在看完这本书后,书中一些概念也不是能够消化和体会,因为在读这本书的时候,就已经感觉到这是一本 深入探讨和介绍业务开发的书,作为一个仅仅意识到业务开发这个坑很深的,仅仅毕业才半年不到的我来说,有些概念还是有点晦涩和无法理解了。但是在读这本书的时候,一个概念出现在我的脑海中并且随着读书的推进一直盘旋在我的脑海--面向对象。是的,OO作为一种设计模式,然后我觉得DDD基本通篇都是在围绕Object这个东西在做讨论。就之前的我来说,因为没有拜读过《设计模式》这本书,所以OO可能只是简单的对一些业务动作的抽象,再直接一点就是数据库中的表的一种映射关系,虽然可能这个概念在现在的我看来是浅薄的,甚至是错误的,但是由于一直接触的是后端的开发,长时间的CRUD和麻木的业务抽象再加上接触的都不是正统的面向对象的语言,所以让我产生了这个观念。因为对于后端来讲,基本都是围绕数据库做开发,围绕业务流程去做开发,数据检验,是否命中缓存,查询数据库,更新数据库数据, 组装数据,更新缓存,发送消息,绝大部分业务的开发基本都是有这些步骤构成的, 面向过程的开发再加上ORM的操作完全可以应对工作中80%以上的代码,之前自己也一直都是这么开发的。但是随后的问题却很多, 写代码的时候受到的影响很多,产品需求的变更,看到的设计,别人的代码风格,所以一旦代码改动的时候常常会有一定范围代码的失控。要修改的代码很多,改动代码之后受影响业务范围不明。我觉得DDD这本书目前对我最大的帮助就是帮我树立起了一些概念:区分业务对象;隔离核心对象;做好抽象,区分每个对象的职责和边界;压缩方法体;压缩副作用函数的范围;简而言之,就是DDD告诉了我应该如何去做一个抽象,如何去做好一个抽象。

首先是要明确的是,业务开发中到底涉及到多少的对象,单纯从后端来讲,一个请求在进入handler的时候,它的哪些参数该封装成一个对象,这个对象在业务中的含义是什么,然后参数的约束自然而然的就变成了对象的一些方法,这些方法就聚合了检验的逻辑,那么一些四散在代码中的校验器就能够很好的被约束起来。调用方需要这个参数的时候,只需要关心是否能够生成对应的对象,这个生成的对象是否能够提供相应的检验方法。通过工厂方法生成参数对象,然后将这个对象在接下来的函数中传递,等到需要不同的对象的时候,就是上下文交互的地方,也就是到达了这个对象的上下文边界,这个时候需要原来的对象提供转换为目标对象的方法,这里需要做约束,最好的方法是目标对象也同样有从原对象生成自身实例的方法,但通常介于这种转换是单方面的,所以一般需要做约束,转换方法是由原对象提供还是由目标对象提供。通常是由目标对象提供的。因为很多时候我们无法更改原对象的实现,就比如调用RPC的时候。需要明确每个对象的生命周期,在什么地方开始,在什么地方结束,如果一个/一些对象贯穿了整个代码或者整个应用,那么对象就是这个业务的核心对象。一些核心对象有的时候是通过一些参数的组合而形成的。而这些参数在早期设计的时候又因为这样或者那样的原因而分散在业务代码中,导致了核心对象其实是一个隐式的对象,这时候就要做一些提炼和重构的工作使得之前的设计重新回归正轨。当我这样做了以后,我发现其实之前一直围绕的ORM对象反而被我压缩在了很小的一块空间之内,或者说它与IO交互的地方被压缩在了一块很小的范围之内。可读性和可测试性都得到了很大的提升。而且一旦代码改动了,或者说需求变动了,所需要的改动被拆分成了不同对象的不同方法,所改动的地方在应用范围内得到了控制。

其实对于后端开发来讲,由于很多时候是和IO打交道,特别是现在微服务很流行的情况下,基本一个服务都会衍生 很多的接口来提供数据或者从其他服务来获取数据,这些接口会衍生出很多的临时对象,而这些对象的生命周期很短,又会被业务内定义的对象取代。而且一个函数内部会封装很多的IO操作,目前的做法是将所有的IO操作都进行单独的隔离,这样的封装会便于调试和明确一些对象的上下文,将其他业务所产生的对象隔离在原业务之外, 然后通过一层转换层将外来对象转换为业务上下文之内的对象,其实运用外来业务的对象也是一种耦合,将自身的业务与其他业务通过对象传递的方式耦合在了一起,这是十分危险的操作。虽然一些转换的代码可能很机械,但是正是因为这些代码的存在才能降低彼此服务之间的耦合程度。我认为不应该将转换逻辑统一的进行封装然后i组成一个通用的库函数。因为这样会引来不必要的依赖,本来对于自身业务对象来说,外来对象的属性应该的由业务内部来自己选择的。DDD中的一个概念很形象,对于一个应用来说,这些代码就像细胞膜一样,控制了哪些东西进入细胞内而哪些东西被隔离在细胞之外。所以model层并不仅仅是最内部的层,它应该也是最外部的层,负责隔离应用内对象和应用外对象。这样的model才是一个健康的模型而不是一个贫血模型。 然后说一下在DDD中学到的另一个很有用的概念:将隐式的条件进行封装然后转变为显示的条件,很多时候的bug都是由一个‘if’引发的惨案,一开始可能if的范围很小,条件也很简单,但随着业务代码的扩张,或者随着对业务的理解逐渐加深,if的条件越来越复杂,越来越难以控制,使得很多地方出现了漏洞,所以对于一些业务中的if,特别是涉及到核心条件的地方,需要将条件转换为显式的条件加以控制,不管这个条件有多么的简单,都要赋予它实际的意义,而不是抽象的比较就被放于业务的主流程中。这并不是过度的抽象,赋予抽象以意义的代码才是能够理解的代码。

扫描二维码,分享此文章

wujintao's Picture
wujintao