最近看到一篇微信文章,是建筑师冯果川在一席中的演讲,标题叫做《我今天讲的所有建筑都是不要脸的建筑》,很受启发。我觉得拿来比喻我一直在考虑的应用架构中业务构件的拆分,是最合适不过的了,因此就果断盗用了其中的关键词——“不要脸”,当然多少也有点标题党的嫌疑。
1.要脸的建筑
冯果川说,中国的公共建筑有一个标准的语法,这个语法就是建筑一定要有一张脸,然后你必须要跟建筑保持一个距离来欣赏这张脸。这种房子是不是只有朝街的这一面给你看?而且为了让你看清楚,它还留了一定的距离,这个距离可以经常被称作广场这样的东西。
比如上海科技馆,前面有一个超尺度的方块广场。怕你观赏的距离不够远,修了个这么大的广场。那个广场有人待吗?门可罗雀。待在那是要接收太阳能吗?
这些建筑都是庞然大物,它搁在这里就是一个几百米的障碍物,你要达到你的目的地必须绕过它,绕得很心烦。
这些高楼落在地上,我们人能走的就是高楼中间那点缝隙,所以这个城市让我们觉得被排斥了。
2.不要脸的建筑
冯果川要把这个地块切碎,让人能够走进去,所以人的漫游和开发商的利益两不耽误,最后可以形成这样的一个效果。你看到这些曲折的路,这些小尺度的庭院,庭院里面很安静,远离了城市的喧嚣。
这一类的建筑,前面没有广场,也没有一个正立面,被冯果川称之为“不要脸”的建筑。
冯果川把要脸的建筑和不要脸的建筑做了个对比:
可以看到,要脸的建筑以大块的空间,阻碍了和人、和自然之间的顺畅交流,空间层次单一而呆板,而不要脸的建筑,把整体的单一建筑分割成可以随意布置的多个小的建筑,不仅丰富了内外的层次和变化,而且也便于和人、和自然的交流。
3.不要脸的业务构件接口
由此我想到了我们的应用架构。过去的单体应用如和要脸的建筑一样,庞大的体量,为了和它打交道,需要一个庞大如广场的接口,才能和它进行沟通。而微服务架构的兴起,正是要把庞大的单体应用切割成不要脸的建筑,切割成一个一个微小的服务,让每个微服务都能够和外界交互、让外部系统可以直接走进来调用。
组成微服务的业务构件本身是否还可以继续拆分呢?
我们先看一个例子。
客户查询自己刚刚添加进购物车的物品信息,发现加入购物车按钮点了两次,结果同一个物品的数量是2个,而不是自己需要的1个,于是点了一下旁边的减号,把数字变成了1,然后保存。
客户的操作分成两步,第一步是查询购物车信息,查询结果的展现包括了要购买的产品的详细信息,包括图片、描述、原价、优惠价等,也包括了购物车相关的信息,如数量、金额等。第二步是修改购物车信息,需要提交的信息有购物车项的编号(页面不可见)、数量、金额,而不需要包括产品相关的信息。
从这里我们可以看到,查询展现的信息和修改提交的信息并不相同,查询一个购物车项,不能仅仅展现产品编号和数量、金额。客户还需要看到详细的产品的图片、描述、单价,才知道到底是否选择了正确的产品进行购买;而提交修改时仅仅需要提交客户能够修改的、属于购物车项本身的信息即可,那些属于产品的、用于查询展示的、并不属于购物车项本身的信息是不需要提交的。
这类场景是普遍存在的,因此需要把查询数据的服务接口和用于生成、修改数据的接口分开,形成单独的、可以复用的服务,以便满足外部各种类型的应用,这种模式叫做CQRS,即命令和查询职责分离。
把对构件的请求拆分为查询和命令两种后,即使是命令请求,按照客户实际的使用场景,对同一个业务对象的增删改的请求也会有多个。
比如说要管理一个商铺的产品,要配置/修改产品的基本信息,配置/修改产品的详情,修改价格,上架,下架等。如果只通过一个Command提供这些所有功能,就会出现以下问题:
1、 接口结构复杂,晦涩难懂;
2、传递的参数庞大,耗费资源,降低性能;
3、 接口参数需要根据文档选择填充,如果没有详尽的文档就很难组织,有时为省事就传递全部参数,浪费资源,降低了性能;
4、接口高耦合,相互影响。
这种单一接口的Command又成为了要脸的建筑,如果把它拆分为多个单一的、职责明确的Command,每个Command只完成一个功能,多个组合起来就完成了全部功能,这个接口就改造成一个不要脸的接口了。
4.不要脸的业务构件的对象
接口拆分完毕后,剩下的就是核心业务逻辑的处理了。对像CRM这一类业务逻辑的处理,采用面向对象的设计和实现方法是比较合适的,因为CRM类业务的对象和关系和自然界中的关系非常类似,也容易进行对象的识别和抽象。但很多业务实现的对象往往成了摆设,真正的业务逻辑依然以成百上千行代码的形式堆积在一个类的一个方法上,使得以后的维护成为噩梦。
而如果真正采用了面向对象的设计和实现方法,通过继承、抽象、关联的设计,把一个构件对象拆分为多个高内聚、低耦合的对象,复杂的逻辑拆分到各自应该承担责任的对象上去,大家协同一起去完成一个Command的业务处理,那么这个构件的核心处理逻辑的也就从要脸的构件改造成一个成功的不要脸的构件了。
比如一个下单的处理过程,在订单对象上完成创建订单、客户确认、付款、完成订单等逻辑。而订单行相关的动作则由不同类型的子订单行去分别进行。
比如订单行的交付方式,对不同的产品,交付的方式是不同的,对于网络类产品,需要在网元上进行开通,对于实物类产品,需要通过快递进行投递交付,而对于内容类产品,需要通过推送或客户主动下载的方式进行交付,这些不同的交付方式由对应的不同类型的订单行分别完成自己的职责是最合适的。
相反,如果这些逻辑全部集中在订单对象的一个方法上去完成,可想而知这个方面会包括多少的if else。
5.结语
去年的一个项目,一个段位颇高的架构师同学,在看了不要脸的构件设计后,质疑说:“一个功能需要22个类去实现,开发、维护不太容易”。用类的数量就能检验开发和维护的难易程度,我也是醉了。如果一定要用数量来衡量,可能也应该用代码行吧?把22个类的代码合并到一个类的一个方法中,难道就容易开发和维护了吗?
相反,把一个类中的一个方法按照业务的本质,采用抽象、继承的策略拆分到本来应该承担自己的职责的类和方法上去,更符合自然,当然也更容易修改、扩展和重构,这和把一个单体应用拆分为多个可以单独部署的微服务是一个道理啊。
要脸的构件改造成不要脸的构件后,如同下图所示:
不要脸的建筑达到了建筑与人、建筑与自然的和谐,不要脸的构件达到了业务构件内部对象之间的和谐关系、业务构件与外部调用者之间自然的和谐关系,这两种不要脸是多么的相像啊。
当然,除了接口和内部业务对象外,还有可能由于设计不合理,把不应该属于该构件的对象放进了该构件中,该构件也许应该拆分为多个构件,当然,这是属于业务建模的范畴。我打算有空的时候,《不要脸》系列的下一篇文章写一写《不要脸的数据模型》。