一、软件生命周期
代码是最容易写的,因为随便怎么写,只要符合语法就能够运行起来,代码也是最难写的,要想让代码容易维护,还要和业务一起长大, 使得软件架构容易随着业务的长大做出新的拆分、合并,并要保证正确性。同时达到这么多的要求是非常困难的。
软件生命周期中有两个最主要的子生命周期:软件开发生命周期和软件运行生命周期。其中,软件运行生命周期是核心生命周期,软件开发生命周期是为软件运行生命周期服务的,所以软件开发时必须要考虑到软件运行生命周期的特质,这一特质也决定了代码应该如何组织和编写。
软件实际上是对现实业务的模拟、虚拟化。结合软件的核心生命周期,也就是运行生命周期,可以看出软件主要是完成对业务生命周期的模拟,在此基础上完成用户的访问生命周期,使得用户能够到达模拟的业务生命周期。因此,代码主要由两部分组成:
- 1、表达业务生命周期的代码。
很多人把这部分叫作业务逻辑( Domain Logic)或者叫业务模型( Domain Model )。这部分是来源于生活与业务, 必须和现实生活及业务保持致,忠实于现实中的业务。 - 2、表达用户访问生命周期的代码。
软件的核心生命周期就是为用户提供对业务的访问,使得业务逻辑能够服务于用户。而为了让用户能够访问到所模拟的业务逻辑,则必须要提供访问通道。表达访问生命周期的代码就是业务生命周期对外的访问通道,软件的核心价值通过这部分代码展现出来。
二、软件代码由三个部分组成
图一要模拟一个完整的人,就需要业务(Busines )部分来实现业务的逻辑,完成对业务生命周期的模拟。业务的状态要靠存储(Repository)来存储持久化,相当于现实生活中的文件柜。
那服务(Serice )是用来做什么的呢?
首先,由于计算机和软件没有自己的意志,因此其内部生命周期的变化就要由外部的人来推动,这时需要提供个访问通道给外部的用户。但如果把业务直接给用户访问,那么用户是很难和业务沟通的,因为这些专业术语,不是用户所能够理解的。 比如客户去银行,接待客户的是更接近用户语言的银行柜员,而不是银行内部的专业业务人员。柜员就是一个服务,以用户听得懂的方式和用户沟通,并把用户的要求转换为业务的语言,再由银行内部的专业业务人员执行相应的操作,柜员最后把执行的结果转换为用户的语言,为其服务。所以在这里服务提供的就是一个访问通道,为用户提供个容易访问银行专业服务的访问方式。
另方面,企业为了接待用户的访问,一定会设个前台。 客户对企业的访问由前台来接待。这样做的好处是,可以防止内部业务人员的工作被外部的访问随意打断,使得内部业务人员可以专心自己的本职工作。如果总是被外部访问打断,业务人员的工作效率就会非常低。此时,这个前台就是一个服务。
最后,不同的用户访问,也要提供不同的服务,以避免不同的用户之间相互影响。比如银行的柜员,对VIP和普通用户就是不同的通道。
服务作为一个通道的含义是什么呢?通道的意思就是不包含业务逻辑。这意味着软件工程师所写的服务代码中是不能够包含业务逻辑的。
从上图可以看出,服务为完成用户访同生命周期,承担的责任包括了组合业务和存储这两个,还要提供给用户访问。这部分的代码任务太多,代码人员的负担比较重,也容易引起服务的代码失控。为解决这个问题,需要把用户访问生命周期再展开分析下。 用户要完成访问业务逻辑生命周期, 需要做如下事情:
- 服务首先要把业务的状态从存储中加载。这是一个生命周期,主体为业务状态获取;
- 服务调用并组合业务逻辑完成业务的访问。这是个生命周期, 主体为业务访问;
- 服务把业务逻辑执行后的状态保存到存储中。这是个生命周期,主体为业务状态保存。
也就是说服务访问业务逻辑的生命周期可以拆分为三个子生命周期。这三个子生命周期中,服务调用并组合业务逻辑完成业务访问是核心生命周期。所以服务还可以继续进行拆分,只保留组合业务逻辑即可,而状态保存则交给单独的组件,就形成了以核心生命周期为主轴,非核心生命期围绕核心生命周期的树状架构,Service -->Glue Code -->Business是核心生命周期,也就是树干,这样存储可以独立应对其变化,服务可以独立应对用户的需求变化,黏合代码(Glue Code)负责从外部存储加载和保存业务逻辑的状态,把业务逻辑包装成具备记忆能力的虚拟人,供服务进行组合调用。
黏合代码是什么意思呢?业务逻辑属于行为是没有记忆的,而存储属于记忆是没有逻辑的。要把行为和记忆黏合在一起,才能够模拟一个人。 因此黏合代码只有做到了这一点,对外才算是一个真正的虚拟人。服务作为通道把黏合代码和用户联系起来,成为了业务虚拟人和用户的桥梁。这和银行的柜员作用是一样的,这就是现实生活如何在软件中实现模拟的。
代码拆分之后,每一个部分都能够独立地变化。用户需求的变化、业务的变化和存储的变化,互相之间就不会产生连锁响应,并可以提升开发的并行度。如下图所示:
图二
三、什么叫业务逻辑
根据以上的代码分工可以很清楚地看到,业务中的代码是最重要的,代码是系统的核心。服务与黏合代码中的代码起通道作用,这是为了让用户的请求能够方问到业务代码。因为真正干活的是业务代码,所以服务和黏合代码中的代码都是访问逻辑。而业务中描述的都是业务的生命周期活动,也就是人们通常所说的业务逻辑。
访问逻辑的特点是组合代码,即常见的顺序调用。这种代码里既不会有计算也不会有if else等判断,只有简单的组合代码,用于组合下层的节点所提供的功能,方便上一层节点的调用。
四、业务逻辑分散的危害
如果业务逻辑不是内聚的就会散落到很多其他地方去,比如散落到服务代码、黏合代码或存储代码中,这会造成哪些问题呢?如果服务代码中混人了业务逻辑,则服务做了两件或者两件以上的事情。典型的情况就是两个不同的访问生命周期合并在一个服务中实现。
比如两个不同类的用户共享一个服务方法,并在服务中判断区分这两类的访间分别处理,这就是人为造成的不必要逻辑。一且某个用户访同生命周期发生变动,共享的另一个访问生命周期的执行就必定会受到影响。被影响的用户访问生命周期自己是不会跳出来说的,因为软件逻辑没有自己的意志,需要用户推动才可以。往往只会等到上线之后,最终用户推动访问生命周期活动时,发现受影响了才会显现出来,此时已经造成生产事故了。
如果有足够的责任感,也会主动去沟通所共享的其他生命周期是否受影响。可是其他共享访问的责任方并没有动力来配合,何况有可能还会给自己增加工作量。这就形成了权责不对等,导致发现问题的沟通成本非常高,因此最后都是不了了之,等待生产事故出现来教训大家。
最可惜的是即便引发了生产事故,责任方还是意识不到这个问题是因为共享了访问通道面造成的,导致类似的问题一面再再而三地发生,修改代码也变得战战兢兢,大家都疲惫不堪。
这就是为什么不同用户的访问通道一定要隔离,不能重用,不能互相影响。必须把这个服务代码拆分,把不同访同生命周期主体的访问通道分离,确保每个服务只做一件事情, 只为一类用户提供通道,确保不同用户的访问生命周期之间没有共享,保证用户访同生命周期本身的内聚,确保不同类型用户的访问通道是独立的。
拆分之后,用户端就要自行组合不同的服务来完成自己的执行流程。如果不拆分的话上线后会出现很多不可预料的问题,最终会因为用户的利益受损而返工,从而也损害了自己的利益。很多软件工程师上线时会没有信心,大部分原因就在此。
五、代码误解
服务代码、黏合代码和存储代码不能有逻辑,很多软件工程师在实际的操作中非常不理解。要么认为这个根本做不到,要么认为增大了工作量。
做到这一点确实需要很多的学习成本,特别是要克服对业务的恐惧。我的游泳教练曾和我说过这些话,令我至今都记忆犹新,大意如下:“业余选手要确保呼吸,总想把头抬起来,身体反而沉下去。只有克服呼吸进水的恐惧,把头往水里压下去,身体才能够从水里浮起来,这样才能真正确保呼吸。真正专业的习惯往往与人们日常的反应相反”。如果真正想快速地完成代码工作,就要克服自己对时间的恐惧,真正的去研究业务的核心生命周期,研究相关利益人的利益,把这个变成日常的习惯。写代码的时候让该出现逻辑的地方出现逻料,让不该出现的地方不要出现。而一旦不该出现的地方出现了逻辑,就要马上采取行动纠正过来。
摘自《聊聊架构》
网友评论