EDA开发原则

近些年,事件驱动架构在架构设计领域受到越来越多的关注。但是,一说到实际应用,很多人就会觉得对现有开发模式改动太大。实际上,事件驱动架构只是在现有的开发模式上,使用事件驱动的方式,对服务或组件进行解耦,提高可维护性和灵活性。

对微服务架构来说,我们经常用MQ服务来进行服务间调用,实现分布式事务。所以,我们实际上已经在使用事件驱动,来代替服务间的调用,解除服务之间的相互依赖。

在领域驱动设计(DDD)领域,可以在领域对象之间避免直接调用,而使用事件进行通信,从而实现领域内的自治。即使这两个领域对象在同一个服务内,也使用事件来驱动领域之间的调用。

在应用开发框架领域,也有很多异步开发框架,也是通过事件来进行组件之间的调用。如VertX,在国外的企业应用开发领域,也有很多的应用。

所以,使用事件驱动架构,并没有想象中那么复杂,只需要一点设计和思维模式上的转变,就能够实现事件驱动的、实时的、可维护、可扩展的企业应用。

事件的第一公民原则
我们传统的开发方式,一般叫分层开发模式,将系统分为展现层、控制器层、业务层、数据库层等。即使是使用微服务架构进行开发,每个服务也都是基于这种方式开发。在这种模式下,最重要的是数据库。我们在设计这种系统的时候,分析完业务流程以后,就会先进行数据的设计。业务层的设计,都是为了将当前的业务状态保存在数据库中。我们设计的原则也是如何更加合理的设计数据,然后更好的操作这些数据,实现业务逻辑。所以,我们可以说我们的第一公民是数据。

然后,基于这种开发模式,我们使用面向对象的方法设计我们的业务系统。为了实现一个业务,我们的用户服务可能要调用订单服务,订单服务再调用库存服务,库存服务可能还要再调用其他服务。这时候如果我们又开发了一个智能库存管理系统,有需要修改库存服务、订单服务等。

但是,我们原本的世界不是这样的。在我们所在世界,我买东西只需要当时选好货物以后结账就可以;我进行库存管理的时候,别人也不会告诉我说是因为有人买了一件商品,所以需要更新库存。在我们现实的世界,所有的一切只是事件而已。肚子饿了就吃,天冷了就加衣服。我们都是在接收到某种信息的时候,才会有一个反应去做某件事情。

所以,在事件驱动架构下,事件才是最重要的,系统现在的业务状态,只不过是所有这些事件作用以后,产生的一个快照而已。所以,事件,是事件驱动架构下的第一公民。

所以,在这个大原则下,我们有一些具体的原则需要遵守。

事件作为一等数据保存
在基于事件的设计架构下,当前的业务状态,都是根据所有的事件的作用而产生的一个快照。虽然在我们的基于事件驱动的微服务架构下,我们还是将当前的业务的数据保存在数据库中,但我们也应该保存我们的事件。当我们将事件保存下来的时候,我们就能够:

如果系统出现了Bug导致数据出错,我们就能根据历史事件分析问题并找到出错的原因。
如果有了新的系统投入使用,新的系统能够使用它需要的这些历史事件,来生成它自己的业务数据,这就能保证我的整个企业范围的数据是一致的。因为我的历史业务,也能在新投入的系统当中体现。这也是我们常说的事件驱动架构的"历史重现"。
事件驱动的范围
一般我们说事件驱动,说的基本都是服务或系统之间的通信,使用事件来驱动服务之间的相互调用。但是,如果我们使用响应式框架在一个应用内实现业务组件之间的相互调用。现在也有很多这样的应用框架,如基于Java的Vert.x,基于Scala的Akka,Axon,和Spring的Reactor都是基于事件驱动的异步开发框架。在Spring Boot2中也开始推荐使用Reactor来进行异步开发。

所以,要使用事件驱动的开发,我们首先要确定事件的范围。如果要使用事件驱动开发框架来开发整个系统,那就需要在整个系统范围内设计事件。

而如果是在微服务系统之间使用事件驱动,就需要确定每个服务内各个业务对象的上下文,确定各个服务之间相互关系,定义事件。

所以在我们设计系统之初,就要根据实际业务和技术选型的结果,来确定事件的范围,在多大范围内使用事件。如果使用现在流行的Spring Cloud框架,那就在服务之间定义事件,以这个事件作为服务之间通信的接口。

服务间的事件接口
不管是在什么范围内使用事件,是服务内使用事件驱动框架使用事件,还是用事件进行服务之间的通信,都要以事件为接口设计我们的系统。

我们现在开发Web服务,不管是用什么语言、什么框架,都能有很好的工具来为我们编写API接口。例如常用Swagger,就能很方便的帮我们生成接口文档,并以在线的方式展示。但是,这种方式展示的接口,并不能从根本上完全区分服务之间的接口和对外服务的接口。只能通过接口权限和注释说明来区分。

如果使用事件驱动的微服务架构,我们就要定义事件,以及事件内包含的所有属性。目前,还没有很好的办法能够对这个事件定义进行很好的管理,只能使用传统的文档的方式。

实际上,我们在使用事件驱动架构开发系统的时候,会使用很多领域驱动设计(DDD)的思想和方法,这就需要一个领域专家,也就是业务专家,来定义系统的业务、领域、上下文、消息、事件等。所以,在事件驱动架构下,并不是因为事件的定义和维护而增加了工作量,而是将这个定义的工作由领域专家来定义,并由服务开发者遵守。并且,当有新的服务启动,或者原有的服务间通信的业务流程发生改变,就需要先进行事件定义的修改,然后根据情况修改相关的服务。

事件的单一职责
在面向对象的开发里,有一个很重要的准则是单一职责原则,也就是说一个类,或一个方法,就只做一点事情,做好一件事情。这一准则是为了降低单个类和方法的复杂性,从而实现一个低耦合、高内聚的类对象。

但是实际上,在实际应用中,能够将这一准则实践好的不多。特别是一个维护了几年的软件系统,业务流程错综复杂,类与类之间、方法与方法的界限已经无法清晰的界定。如果两种业务做的事情类似,很多时候,我们为了实现代码重用,就让一个方法做这两件事情。久而久之,代码的结构也会越来越乱,也变得越来越难以维护。

在事件驱动架构设计的时候,也会遇到类似的问题,也就是事件的粒度的定义。如果以最细的粒度来定义事件,那么就是,每个领域对象的每一种变化,都定义一个事件。例如一个订单的创建、提交、修改、审核通过、审核不通过、删除,等等,都定义相应的一个事件。那么所有需要使用该事件的服务也都订阅相应的事件。

可以看到,这样的方式定义的事件,可能会很多,事件定义多了,任何一点业务的变化可能都需要更新事件定义的文档,再修改相应的服务。同时,服务对事件的订阅也会很多。

但是,我们应该从业务上去解决这个问题,应该更好的定义我们的领域和上下文,定义领域对象产生的事件。我们应该以细的粒度定义事件,就好像面向对象的单一职责原则一样,也要让我们的事件具有单一职责,具有单一的业务定义,然后再由相应的服务来订阅该事件进行处理。

管理事件的订阅
我们定义事件,除了要从发布者的角度出发定义事件及其包含的数据,也需要考虑订阅者对数据的使用。

对事件驱动的微服务来说,订阅者所在的服务很可能无妨直接访问发布者的数据,这就需要在事件里面包含订阅者需要用到的数据。

其次,对一个事件,有哪些订阅者在使用,也需要进行管理和维护。这样,我们才能在需要更新事件数据的时候,知道有可能影响哪些服务,并如何保证事件的订阅者在使用该事件的时候不会出错。

事件数据结构的更新
在企业应用中,由于业务的变化而产生的事件的变化,是在所难免的,这时候我们就需要:

保证订阅该事件的所有订阅者的功能正常。
如果事件被保存,而新的事件的结构发生改变,这时候如果还想进行历史重现,也就是在历史的事件上重新调用订阅方法,重新生成现在的业务数据,那么就需要订阅方法技能处理就的事件数据,也能处理新的数据,也就是向前兼容。
所以对于事件的改变,一定要慎重,也就是需要领域专家来定义和维护事件,而不是随意由事件的发布者进行修改。

响应式编程的本质
事件驱动架构,实际上是一种响应式编程的实现方式,在响应式编程当中,我们不是依次调用一个个的业务方法,而是通过事件触发一个个的业务方式来执行。

在响应式编程中,一个业务流程由一个事件触发,这个事件可能是一个消息队列的消息,也可能是一个web请求,一个服务被这个事件触发,处理完自己该做的事情以后,发送另一个事件,触发下一步操作。以此下去,用一种流的方式完成这个业务。

系统内的多个服务完成一个业务流程是如此,一个服务也是通过流的方式不断的接收新的事件,处理并将结果生成新的事件,产生新的事件流。

所以,响应式编程,其本质就是:一切皆为流。

猜你喜欢

转载自blog.csdn.net/weixin_44264245/article/details/86080159
今日推荐