写代码精进之路:快反而是慢?巧用DDD设计,慢中变快!
The following article is from 程序架道 Author 程序架道
周一来到,小明接手了一个需求。
要搞一次促销抽奖活动,奖池里配置了很多奖项,我们需要按运营预先配置的概率抽中一个奖项。
解决思路貌似很简单,生成一个随机数,匹配符合该随机数生成概率的奖项即可。
实现貌似也很简单。
很快,弄了两张表。
很快,弄了三个类。
很快,搞定,上线,花费时间不到半天,小明很得意。
可是,需求变化也很快。
产品经理找到小明,规则变化了,要增加过去两个月内购买过10个订单以上的用户才可以参与抽奖。
小明应该把代码添加到哪个位置呢?
需求再次变化了,未来三天购买订单数满足跨店才能参加抽奖。
小明应该把代码添加到哪个位置呢?
即使后面来的两次需求,都找到了自己的添加位置,第四次、五次。。。呢。
每次添加新代码的时候,如何快速的定位?
开发完成后,测试人员要不要全回归测试呢?
毕竟你都改动到了好几处地方了,如果不全回归,以前的代码会不会有问题呢?
如果这样都做了,后面没增加一个需求变化,到底是快,还是慢了呢?
业务逻辑复杂了,业务的逻辑、状态会散落到大量方法中,你没有抽象,就没有办法模块化,就不能区分核心和周边,需求越来越多,你就只能硬写,你的这种硬写,往往都是写到了核心模块里面了,之所以成为核心,不就是希望你不要总是改变它吗,要尽可能将其变为只读的,否则,你当初的快就是后来的慢;
上面的编程方式是哪种方式呢,什么编程风格?
基于“Service + 贫血模型”的实现。
大家,为什么总是习惯用上面那种方式编写代码呢?
可能是业务简单到就是基于SQL的CRUD。
可能是在service层中可以定义任何操作。
可能是思维已固化。
可能是转型成本太大。
可能是。。。
如何应对变化,如何不让当初的快,变成后面的慢呢。
就是要千方百计地将核心模块和周边模块,变成正交性的设计,让核心模块变成只读,每次来一个需求只需要修改或增加周边模块就好了。
那如何才能一步一步实现正交设计的代码呢,最原始的基础就是要用丰满的面向对象技术,用丰满的面向对象技术的基础方法又是充血模型。
基于贫血模型的传统的开发模式,比较适合业务比较简单的系统开发;相对应的,基于充血模型的 DDD 开发模式,更适合业务复杂的系统开发;比如,包含各种利息计算模型、还款模型等复杂业务的金融系统;
应用基于充血模型的 DDD 的开发模式,需要事先理清楚所有的业务,定义领域模型所包含的属性和方法;领域模型相当于可复用的业务中间层;新功能需求的开发,都基于之前定义好的这些领域模型来完成;
越复杂的系统,对代码的复用性、易维护性要求就越高,就越应该花更多的时间和精力在前期设计上;而基于充血模型的 DDD 开发模式,正好需要前期做大量的业务调研、领域模型设计,所以它更加适合这种复杂系统的开发;
好了,小明也觉得使用丰满的对象编程技术,会比较好。
那问题来了,小明需要第一次,就按照这样的编程风格编程吗?
第一次就需要考虑那么的周全吗?
第一次就需要面向未来设计吗?
我个人的建议,你可以被子弹打中一次,但是不要被打中第二次。
为什么这样说呢。
对这个图还有印象吗,整洁架构,向内依赖,最”内“里面是什么呢。
是个领域模型。
如果你第二次,第三次依然没有抽象出领域模型,你的每一次以为的快,都是为后面每一次的慢,埋下了“因缘”。
有没有好的策略,来指导如何判断要不要搞成所谓的领域形式呢。
个人建议:
1、判断是否你的程序只为一个业务方服务。比如财务人员要用到、营销人员要用到、运营人员要用到。如果是,就要提前考虑沉淀出业务领域模型。
2、判断是否你的程序只为一个业务模式服务。比如拼团业务要用到、国际业务要用到、健康业务要用到。如果是,就要提前考虑好业务身份的判断且抽象共享服务。
有没有好的原则,我按照这样的原则进行设计,进行开发就是能符合”高大上“的技术范的领域模型呢。
个人建议:
1、SOLID设计原则和23设计模式,优选SOLID设计原则。你问我为啥这样选,打个比喻的话,SOLD就像独孤九剑,23设计模式就像九阳真经。练招式起步容易,而且那些SOLID原则里面所包含的内容的内核,你只要”细思极恐“下,暂且用这个词语,一样能有一翻“洞天”。
练习独孤九剑也需要悟得。
当你可以将这其中的5个设计原则能够融汇贯通的时候,也是你的任督二脉打通之时。
当然,还有其它简单易行的原则,比如DRY、KISS、YAGNI、LOD等等。
2、增加辅助措施,没有规范,则显凌乱,若要有序,执行编程规范,同时把代码重构养成日常行为。
小明想,思维建议策略有了,设计指导原则也有了,有没有代码让我学习一番呢。
如今,学习,最不缺的就是代码,看开源软件。
Tomcat程序就是非常优秀的开源软件,你不会不这样认为吧,你的程序打包都发布到它的容器里面了,这里面有非常优雅的设计。
试想,Tomcat这样的web容器是怎么样接收我们的http请求的呢。
我们的queryOrder请求为什么就被送到相应的动作上执行的呢。
Tomcat要为每一个请求都加一个if else来判断,才能实现不同动作请求到不同类文件上面吗。
如果都加上这样的if else判断,是要在业务类里面增加吗,那不就跟业务类耦合了吗。
因此,Tomcat的程序的设计者们,想了很优美的设计思路。
他们发明了 Servlet 容器,Servlet 容器用来加载和管理业务类。HTTP 服务器不直接跟业务类打交道,而是把请求交给 Servlet 容器去处理,Servlet 容器会将请求转发到具体的 Servlet,如果这个 Servlet 还没创建,就加载并实例化这个 Servlet,然后调用这个 Servlet 的接口方法。因此 Servlet 接口其实是 Servlet 容器跟具体业务类之间的接口。
图自 李号双
这个设计里面,充满了浓浓的我们先前说的SOLID味道和正交设计的味道,你可以找出来吗。
只有当我们具备了,良好的开抽象维意识,开闭认知意识,原则学习意识,分合执行意识,实践到了,也就摸到了,我们写程序起初的慢,才是后续增加需求的快。
----END----
这里记录,我每周碰到的,或想到的,引起触动,或感动的,事物的思考及笔记。不见得都对,但开始思考记录总是好的。
与爱学习、爱思考、爱记录的你共勉。
参考资料:
https://tech.meituan.com/2017/12/22/ddd-in-practice.html 《领域驱动设计在互联网业务开发中的实践》 美团技术团队
https://www.cnblogs.com/wod-Y/p/12337721.html 《如何利用基于充血模型的DDD开发一个虚拟钱包系统》杨海星
https://time.geekbang.org/column/article/95480?cid=100027701 《你应该知道的Servlet规范和Servlet容器》李号双
往期推荐:
技术琐话
以分布式设计、架构、体系思想为基础,兼论研发相关的点点滴滴,不限于代码、质量体系和研发管理。