作者:潘吉祥
虽然我们使用面向对象的语言,但是这并不意味着我们在软件开发过程中就是面向对象的。面向对象的重点在于从现实世界去映射软件,而不是完全地从数据设计构建软件。
这一方面突出的说明是贫血模型和充血模型,因为小型的系统使用数据驱动+贫血模型(PO)能够快速构建起来,以至于我们再转向大型系统的时候依然沿用了这种设计,这更适合我们的编程习惯,包括大量简化工作的框架,都促使我们的项目大量采用数据驱动+贫血模型 的设计。
比如我们为用户设计一个钱包,往往是建一个对应的表,关联用户id,生成一个对应的PO类,类中只有get/set方法。当我们执行用户扣款的时候,往往会在调用dao执行数据库更新前写一段钱包余额是否足够此次支付的逻辑(有时候我们也可能将判断条件直接跟在SQL中用于乐观锁判断)。
我们可能有多个地方操作到了钱包金额,这样的话我们需要写多次这样的判断逻辑,或者我们将这段判断抽象成为一个工具类,无论是那种做法,最后导致的结果就是代码分散,就像我们开门之前需要解锁,而我们把锁放到了自己的裤兜里,每次开门前从裤兜里掏出锁开锁,然后开门进去。事实上,锁应该跟门强关联,不开锁就不让你进,这是门应该做的事情(这听起来有些荒唐,但有时候我们的软件确实是这样设计的)。
那么按照面向对象的设计,这个判断应该由钱包实体自己执行,无论是在钱包实体(这里的钱包对象并不是说就是那个钱包PO)中定义,还是在钱包实体的中定义金额值对象,然后在金额值对象中判断。我们只需要输入金额参数即可,具体的判断逻辑由钱包实体自己管理,而不会导致逻辑像之前那样分散,同时符合我们的现实逻辑。
关于以上的实体和值对象,这里不详细展开,相关概念可以参考领域驱动设计相关文献(本公众号也有相关内容文章)。上面的描述主要在说明当前的很多所谓的“面向对象开发”的不足,因此,我们在软件的设计方面,遵循恰当的面向对象(面向显示世界)的思维是更加先进的一种做法。
当前应用开发中用到的驱动模式大概可以分为三种:
1请求驱动:请求方向指定服务方发出request,服务方(服务方本身为了完成这个响应也可能会向另一个服务发出request)返回一个response。
2定时任务:指定时间段之后执行或者固定周期内轮询执行。
3 事件驱动:监听需要的事件消息,根据事件消息执行响应的逻辑。
定时任务应用领域范围比较狭窄,仅限于一些特定的需求,典型的是一些批量异步任务,和一些周期性的逻辑动作,我们不展开详细说明。
请求驱动:映射到现实世界,是这样的,假如我们生活在春秋时代,有机会向孔子讨教学术问题,那个时候交通不便,我们为了一次讨教可能要花费很长时间在路上。由于先生非常有名气,向他讨教的人非常多,去了可能我们还要排队,这样做,只是为了得到一个解答。而且每次都要如此,大费周章,有时孔先生还可能周游各国,不在家。
事件驱动:还是拿孔先生的例子,每个国家都建设了孔先生的图书馆,孔先生每当有新的思想知识就写出来,然后有相应的人员运输到各个国家的孔子图书馆,于是人们就可以轻松地学到新鲜的孔先生的知识。或许许多人都未曾见过孔子,但那完全没有关系,即使孔子仙逝,人们依旧能够学习到(拒绝抬杠……)。
更加宽泛一些,我们每个个体都是通过信号驱动来实现自理的,天气冷了我们会自己添衣,天热了我们会自己减衣,而不是别人跟我们说冬天到啦,或者夏天到啦我们才开始发生响应的反应。我们的这种自理映射在软件方面,尤其是云原生背景下,就对应着服务自己的自治性。
“云原生软件是高度分布式的,必须在一个不断变化的环境中运行,而且自身也在不断变化”,其中“高度分布式”和“不断变化的环境”是软件新时代所面临的挑战性问题,“自身不断变化”是解决面临的问题的落点,因此我们最终的关注点是实现它:“自身(构成整个应用的所有个体服务)的不断变化”。
“自身不断变化”包含两个层面:
1 历史阶段发展。怎么理解呢,用人类的发展来类比软件的发展,人类社会经历了原始社会、奴隶社会、封建社会、资本主义社会到社会主义社会,软件也同样经历了单体、集群、到SOA服务化。
2 SOA阶段服务个体的自治。当我们在特定的一个软件发展阶段,整个应用被无数个服务组成的时候,服务作为个体应该具有实时的自治性,随着由其它服务的变化信号自动矫正自己的相关数据,同时发出信号给其它服务,这看起来就像他们内部在自己交流。
第一个层面往往不是我们能够凭借主观的意愿所能够改变的,更多是一种时间的沉淀积累和创新,我们能够掌握的是第二个层面:采用事件驱动构建个体服务,而不是单体时代遗留的请求响应方式。
我们可以说事件驱动是异步的,也通常使用一些消息中间件来实现这种架构,但是事件驱动绝不等于异步,事件驱动也不简单等于消息解耦,事件驱动是一套完整的服务建构方案,它不仅包含了技术层面,同样需要良好的设计思想。
首先“事件”本身决定了它是一个过去式层面(xxxed)的概念,因此它具有已完成和不可修改两个重要属性。同时事件作为服务运行的依据,它是一个技术层面的概念,然而技术是服务于业务的,不同的业务产生特定的事件,因此它还是一个业务概念。
事件驱动架构以“事件”为核心,通过事件实现服务之间最松耦合的编排,构建更符合云原生时代的应用。在事件驱动架构下,服务不需要知道其它服务的存在,也不关心它是否存在,每个服务只要做好自己的事件监听和事件发送即可,这个系统就是运行态的。
因为消息系统是实现事件驱动的大动脉,因此事件驱动天生具有异步的特性,这也符合当前软件设计异步化的历史变革,这也是它的一大优势。
另一方面,事件驱动的服务根据事件信号自动运行,通过为每个服务添加本地存储,可以实时更新不属于自己服务的数据,形成业务闭环,避免原始的SOA业务逻辑被动跨越多个服务系统,降低耦合的同时,大大提高了内聚,而这主要是设计方面的功劳。
同样的,事件驱动设计方面往往比技术层面更加重要。事件不等于消息,消息传输payload,消息一旦被消费就应该消失。消息的关注点在于消息是否投递和消息是否被消费成功。事件作为服务运作的核心依据,将保留为可重播的历史记录。事件不直接对接消费者。事件是已经发生的事情的记录,因此无法修改且不可删除被永久保存。
采用事件驱动的服务体系,往往使用最终一致性,而不是2PC或者相关变种分布式事务协议,分布式事务不应该成为大型系统的瓶颈点。而且事件驱动架构趋向于整个事务正向补偿前进,而不是错误终止回滚处理。从理论上来说,一旦用户执行了某个操作(除非误操作,当然这不算入错误),表名用户想要达到某个目标,基于事件消息的架构,我们更容易让整个流程补偿向前发展。
此外事件驱动架构已经有了许多成熟的设计模式,学习这些模式能够让我们的事件驱动架构更加合理和价值最大化,而不是单纯的一个消息中间件解耦的系统。在后续文章我将依次介绍这些有用的设计。
Ps:相关疑问可在评论区打出,笔者将视情况增加特定内容。