当前位置:   article > 正文

一周技术思考(第30期)-不要成为过度工程化的牺牲品

避免过度工程化

 大家好,这里记录,我每周读到的技术书籍、专栏、文章以及遇到的工作上的技术经历的思考,不见得都对,但开始思考总是好的。

 

1

Spring是工程化,依赖注入框架,可以允许用户通过约定或者注解,将应用程序对象之间的关系外部化,而不是在对象内部彼此硬编码实例化代码,以便更轻松地管理大型Java项目。

 

J2EE是工程化,我们常用的Action/Service/DAO这种分层模式,将应用程序内部的文件进行分层,使得关注点分离,以便每一层更专注自身的职责。

 

DDD和微服务是工程化,将现实的业务场景按照领域边界划分,以便将可复用的业务能力沉淀成中台业务模型,实现企业级的业务能力复用。

 

工程化,可以是一种工具框架,可以是一种编程风格,可以是一种设计思想。每一种,都曾和正在给我们带来生产力,影响着我们的代码效能。

 

但,每一种,如果你过度依赖,就有可能成为它的奴隶,成为过度工程化的牺牲品。

 

本周,从我这周在网上看到的一个例子开始,参考文章连接我放在了文后。

 

2

以前互联网,都是提倡“快速迭代,小步快跑”,一顿CRUD操作,快速上线,业务照跑。

 

就是下面这个样子。

 

说有一个需求场景,要搞一次促销活动,设置了奖池,奖池里面可以配置很多奖项,每个奖项对应一个重奖概率。实现的思路生成一个随机数,匹配符合该随机数生成概率的奖项即可。

 

有一个被公认为日常开发效率很高的研发人员说,这个需求很简单,我来设计。

 

图1

 

于是,他很快的设计了两张表,一直都是这么个设计思路,也没有什么不对,数据表驱动。

 

另外,还设计AwardPool和Award两个对象,但是,只有简单的get和set属性的方法。

 

  1. class AwardPool { int awardPoolId; List<Award> awards; public List<Award> getAwards() { return awards; } public void setAwards(List<Award> awards) { this.awards = awards; } ......}
  2. class Award { int awardId; int probability;//概率 ......}

 

最后,再设计一个LotteryService,在其中的drawLottery()方法写服务逻辑,齐活。

 

AwardPool awardPool = awardPoolDao.getAwardPool(poolId);//sql查询,将数据映射到AwardPool对象for (Award award : awardPool.getAwards()) {   //寻找到符合award.getProbability()概率的award}

 

很快做完了这个需求,真的很快。

 

3

这得益于他使用了一套成熟的生产框架,也就是我们已经熟悉,而且我们习惯了的J2EE开发模式,Action/Service/DAO这种分层模式。

 

上面说的是“以前”,那么现在呢,现在也有上面那样的代码,但似乎这些年开始有了变化,互联网也开始DDD了。

 

DDD是啥,至少20多年前就有人开始提出,要在软件开发中进行领域建模和设计。既然有上面那样的“以前”那么快的开发方式,那为什么现在各大厂又都开始推行DDD了呢

 

这样做岂不是又重回古老而缓慢的做法吗。

 

实际上,你研究下各大互联网,近些年在干的方向,就能明白其中的原因了。

 

大厂们的业务逐渐深入到了实体经济,线下门店、全渠道运营等等,业务日益复杂,再也不是单单搞几个纯电商的促销活动那样简单了。互联网企业在开发中也越来越多地遇到传统行业软件开发中的难题,这个时候以业务领域驱动编程的DDD便重新登场了

 

4

如今,我们都能看出来了,上面那段代码实现的思路是一个“贫血”的设计。其中的对象只是个数据对象,没有任何动作,仅仅用作数据载体。

 

不得不说spring最伟大的地方就是能让不懂面向对象编程的研发人员,也能写出面向对象的程序。但是这里的面向对象,并不是你写的,Service和DAO这样的对象是spring框架负责生产的,而你,只是在service里面写了一堆过程方法。

 

你实际上是在做面向过程编程

 

当一个贫血的对象被更多的service方法调用的时候,也就是业务逻辑和状态开始散落在大量的方法中的时候,这时代码的耦合性变得愈发严重,研发人员修改代码的代价也越来越大,因为本来应该在实体中的动作,都早已跑到了各个方法里面去了,有点共性的修改,你不得都不到处缝补

 

这时,我们才会发现,原来的快,再也快不上去了,一路走来,它的真正轨迹是,快、慢、更慢

 

5

软件是天然复杂的,目前还没有哪个理论或者方法能够消除这样的复杂性,这样的复杂性又叫做本质复杂性。

 

本质复杂性,是由待解决的问题所引起的,是问题固有的复杂性,它是与问题相关的复杂性,是无法避免和消除的。

 

比如,某个业务功能要调用A、B、C三个接口,而且他们的顺序要严格保持线性,那么这三个接口是必不可少的,程序必须完成这三个接口所包含的业务逻辑。

 

我们谈到了刚才提到的软件复杂度是天然的说法,但是,你确信上面奖池促销那个场景的程序所包含的复杂性是我们说的天然的复杂性吗。

 

我们要能够识别出此时此刻软件的复杂度是不是我们因为代码的设计不足所导致的,更确切点说,是不是我们自己的原因导致的,如果是这种情况,那么这样的复杂度就是一种偶然复杂度。

 

偶然的复杂性是由软件研发人员制造出来的并能够处理的与问题相关的复杂度。这样的复杂性是偶然的,可能是由某些工程师没有足够的思考就把某些组件不必要地联系在一起所导致的。

 

偶然复杂度是可解的。

 

偶然的复杂度并不是阻碍我们继续改变软件生产效率的拦路虎。

 

比如,上面因为我们当初设计了贫血的模型,从而导致了,当有很多个业务场景都来使用奖池和奖项对象的时候,我们把业务逻辑散落在了各个方法里面,这其中包括了共性的业务逻辑,而我们在这样的模式环境下,增加实现一个新的需求的时候,往往我们不是修改当前需求所要改动的代码,很有可能也会触及到其它场景的代码。

 

在这样的复杂的业务逻情况里面,我们就可以将其变成充血模型设计,将共性的,本该是实体业务对象自有的业务逻辑动作下沉到业务实体对象中去,让我们的对象具有动作,以便能够反映一个真实的业务实体。

 

从此,我们所学的很多关于OO的理论,OOA、OOD、OOP,都有了用武之地。

 

6

没有人希望自己所构建的服务是贫血的——只是执行些琐碎的增删改查(CRUD)操作。这些贫血的服务通常是导致系统内部耦合严重的源头之一。同时,我们要避免将太多的责任放到一个服务中,低内聚的服务会使得修改软件时效率更低,风险更大——而这恰恰是我们试图避免的。

 

可以看的出来,我们作为架构师或者作为高级研发工程师,是被要求有这样的判断能力的,不然,我们一味使用传统的J2EE的贫血开发模型,或者一味使用DDD所提倡的充血模型,都不是完全合理的,要结合实际的判断来做决定。如果,我们没有这样的判断能力,而是盲目地选择了DDD或者常规的J2EE的架构方案,却并不是以“产品或者业务领域的实际复杂性”为依据,那么我们就很可能成为过度工程化的牺牲品

 

7 另外一个话题

接上一周。

 

我们梳理了一个在线投资系统中的订单交易环节,共涉及到的服务有order服务、account transaction服务、fee服务、market服务。这些服务之间通过协调,目的是提交一个出售订单。

 

过程如下图所示。

 

图2

 

现在有一个新的需求:

 

用户希望把自己一定数额的股票,设置成定时交易,条件是,当A股票价格达到79元的时候,就立刻出售,期限是在未来一个月的时间里。

 

那么,这个时候,基于原有的服务系统,我们有如下三种方式可选。

 

对现有的服务接口进行扩展;

添加一个新的服务接口;

添加一个新的服务;

 

此时此刻,作为负责这项需求的研发的你,应该如何选择呢。

 

欢迎讨论。

8 另另外

本周听得到,听到一句话,“人们总是太高估自己能在一年里做到什么,而又总是太低估自己能在十年里做到什么。”

分享给你。

恭喜你,又完成一次思考。

 

 

参考资料

1、https://tech.meituan.com/2017/12/22/ddd-in-practice.html 领域驱动设计在互联网业务开发中的实践,文中的图1页来自这篇文章

2、《微服务实战》,文中的图2选自本书第2章

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/699004
推荐阅读
相关标签
  

闽ICP备14008679号