每当微服务中运用领域事件

[转]:http://www.cnblogs.com/davenkin/p/microservices-and-domain-events.html

在微服务中行使世界事件

 

稍许回想一下处理器硬件的办事原理我们就一拍即合发现,整个电脑的劳作经过实际上就是一个针对性事件的处理过程。当你沾拍鼠标、敲击键盘或者插上U盘时,计算机便以中止的款型处理各种外部事件。在软件开发领域,事件驱动架构(Event Driven
Architecture,EDA)早已为开发者用于各种履,典型的运用场景比如浏览器对用户输入的拍卖、消息机制同SOA。最近几年更上开发者视野的响应式编程(Reactive
Programming)更是用事件作该编程模型中的如出一辙相当于百姓。可见,“事件”这个概念一直以处理器是领域受到扮演着重大之角色。 

 

 

识世界事件 

领域事件(Domain Events)是领域让设计(Domain Driven
Design,DDD)中之一个概念,用于捕获我们所建模的领域受到所产生了之业务。领域事件本身也作通用语言(Ubiquitous
Language)的一模一样有改为包括领域专家在内的装有种类成员的交流用语。比如,在用户注册过程遭到,我们也许会见说“当用户注册成功之后,发送一封闭欢迎邮件被客户。”,此时底“用户已经登记”便是一个天地事件。 

 

本来,并无是兼具发生过的事体还可以变成世界事件。一个天地事件要对工作发价,有助于形成整体的作业闭环,也就是一个天地事件将促成更的业务操作。举个咖啡厅建模的例证,当客户来前台时以发生“客户已抵达”的风波,如果你关心的凡客户接待,比如用也客户留下位置等,那么这之“客户已经到”便是一个名列前茅的小圈子事件,因为其以用于触发下一步——“预留位置”操作;但是一旦你建模的凡咖啡结账系统,那么此时的“客户已到达”便没多异常有的画龙点睛——你无可能以用户达时即便立刻向客户要钱对吧,而”客户就下就“才是本着结账系统中之轩然大波。 

 

在微服务(Microservices)架构实践着,人们大量地借用了DDD中之概念和技能,比如一个微服务应该相应DDD中的一个边际上下文(Bounded
Context);在微服务设计受到应该首先识别出DDD中之聚合根(Aggregate
Root);还有以微服务之间并时采用DDD中的警备腐层(Anti-Corruption Layer,
ACL);我们甚至可说DDD和微服务有着天生的默契。更多关于DDD的情节,请参见笔者的另一篇文章或参照《领域让设计》及《实现世界让设计》。 

 

在DDD中产生同漫长标准:一个政工用例对应一个政工,一个工作对应一个聚合根,也即当同一潮事情中,只能对一个聚合根进行操作。但是在实际上使用中,我们经常发现一个用例需要修改多独聚合根的图景,并且不同之聚合根还地处不同之界线上下文中。比如,当您于电商网站及采购了物下,你的积分会相应增多。这里的贩行为或受建模为一个订单(Order)对象,而积分可以打模成账户(Account)对象的某个属性,订单和账户都为聚合根,并且分别属于订单系统与账户体系。显然,我们需要以订单和积分之间维护数据一致性,然而当与一个事情中同时创新两者又违背了DDD设计标准,并且这亟需在少数只不等之系内采用重量级的分布式事务(Distributed
Transactioin,也受XA事务或者全局工作)。另外,这种办法还当订单系统与账户体系中产生了强耦合。通过引入世界事件,我们好充分好地解决上述问题。 

 

看来,领域事件被咱带以下好处: 

  1. 解耦微服务(限界上下文)

  2. 辅助我们深刻了解领域模型

  3. 供审计和报告的数目出自

  4. 迈向事件溯源(Event Sourcing)和CQRS等

 

要么以上给之电商网站也例,当用户下单之后,订单系统以生一个“用户已经生单”的天地事件,并揭示到消息网中,此时下单便好了。账户体系订阅了信网面临的“用户都生仅仅”事件,当事件到时进行拍卖,提取事件备受之订单信息,再调用自身的积分引擎(也有或是外一个微服务)计算积分,最后更新用户积分。可以看出,此时底订单系统以发送了事件之后,整个用例操作就结束了,根本无须关心是何人收了轩然大波还是对事件做了呀处理。事件的消费方可以是账户体系,也可是外一个对事件感兴趣的老三正,比如物流体系。由此,各个微服务之间的耦合关系就解开了。值得注意的某些是,此时相继微服务之间不再是青出于蓝一致性,而是因事件之末段一致性。 

 

 

 

事件风暴(Event Storming) 

事件风暴是千篇一律件团队活动,旨在通过世界事件识别出聚合根,进而划分微服务的界限上下文。在走受到,团队先经过脑风暴之样式罗列有天地中负有的领域事件,整合后形成最终的园地事件集合,然后于各一个轩然大波,标注出招该事件的指令(Command),再然后也每个事件标注出命令发起方的角色,命令可以是用户发起,也得以是第三正系调用或者是定时器触发等。最后对事件展开分类整理出聚合根以及限界上下文。事件风暴还有一个格外的功利是得变本加厉与人员针对天地的认识。需要专注的是,在事变风暴活动受到,领域专家是须到之。更多关于事件风暴的情,请参见这里。 

 

 

 

 

 

始建世界事件 

领域事件应该对“什么人啊时候召开了哟事情”这样的题材,在实质上编码中,可以设想下臃肿超类型(Layer
Supertype)来含有事件的一些共有属性: 

 

NoSQL 1

public abstract class Event {
    private final UUID id;
    private final DateTime createdTime;

    public Event() {
        this.id = UUID.randomUUID();
        this.createdTime = new DateTime();
    }
}

NoSQL 2

 

可观看,领域事件还隐含了ID,但是该ID并无是实业(Entity)层面的ID概念,而是要用来事件追溯和日志。另外,由于世界事件描述的凡过去时有发生的业务,我们应有将世界事件建模成不可变的(Immutable)。从DDD概念上讲话,领域事件更如相同栽特殊的价值对象(Value
Object)。对于上文中提到的咖啡吧例子,创建“客户就达”事件如下: 

 

NoSQL 3

public final class CustomerArrivedEvent extends Event {
    private final int customerNumber;

    public CustomerArrivedEvent(int customerNumber) {
        super();
        this.customerNumber = customerNumber;
    }
}

NoSQL 4

 

于这CustomerArrivedEvent事件备受,除了继续自Event的性能外,还起定义了一个与拖欠事件密切关联的事务特性——客户人数(customerNumber)——这样持续操作就可养相应数额的席位了。另外,我们用有着属性与CustomerArrivedEvent本身还宣称成了final,并且不为外暴露任何可能修改这些性的方法,这样就保证了风波之不变性。

 

 

披露领域事件 

在行使领域事件不时,我们平常用“发布-订阅”的方来并不同的模块或系统。在单个微服务内部,我们可以下领域事件来并不同之效力组件,比如以上文中干的“用户注册之后为用户发送欢迎邮件”的例证中,注册组件有一个轩然大波,邮件发送组件接收到拖欠事件后为用户发送邮件。 

 

 

 

当微服务内部以世界事件频仍,我们无肯定不得引入消息中间件(比如ActiveMQ等)。还是以上面的“注册后发送欢迎邮件”为条例,注册行为与殡葬邮件行为则通过世界事件并,但是他们仍然有在和一个线程中,并且是齐的。另外待小心的凡,在分界上下文之内用世界事件频仍,我们还亟待遵循“一个事务只更新一个聚合根”的格,违反的亟代表我们本着聚合根的拆分是拂的。即便真存在这样的情况,也应当经过异步的法(此时需要引入消息中间件)对两样之聚合根采用不同的工作,此时可设想采用后台任务。

 

而外用于微服务的内部,领域事件又多的凡于用于集成不同之微服务,如齐文中的“电商订单”例子。 

 

 

 

寻常,领域事件产生让世界对象被,或者又纯粹的就是产生为聚合根中。在具体编码实现时,有强法可用来发布领域事件。 

 

同样栽直接的措施是于聚合根中一直调用发布事件的Service对象。以上文中的“电商订单”为例,当创建订单时,发布“订单已经创造”领域事件。此时得以考虑于订单对象的构造函数中发布事件: 

 

NoSQL 5

public class Order {
    public Order(EventPublisher eventPublisher) {
        //create order        
        //…        
        eventPublisher.publish(new OrderPlacedEvent());    
        }
}

NoSQL 6

 

 

流动:为了将关键集中在波公布上,我们针对Order对象做了简化,Order对象自我在实际编码中不备参考性。 

 

得视,为了发布OrderPlacedEvent事件,我们得以Service对象EventPublisher传入,这眼看是一样种API污染,即Order作为一个天地对象只是需要关爱以及事情有关的数据,而不是诸如EventPublisher这样的根基设备对象。 其余一样栽办法大凡由于NServiceBus的祖师Udi
Dahan领到出来的,即在领域对象吃通过调用EventPublisher上之静态方法发布领域事件:

 

NoSQL 7

public class Order {
    public Order() {
        //create order
        //...
        EventPublisher.publish(new OrderPlacedEvent());
    }
}

NoSQL 8

 

这种方式虽然避免了API污染,但是此地的publish()静态方法将起副作用,对Order对象的测试带来了难。此时,我们得采用“以聚合根中即保存领域事件”的方法给予改进:

 

NoSQL 9

public class Order {

    private List<Event> events;

    public Order() {
        //create order
        //...
        events.add(new OrderPlacedEvent());
    }

    public List<Event> getEvents() {
        return events;
    }

    public void clearEvents() {
        events.clear();

    }
} 

NoSQL 10

 

于测试Order对象时,我们即便你可以透过验证events集合保证Order对象在开立时真发表了OrderPlacedEvent事件:

 

NoSQL 11

@Test
public void shouldPublishEventWhenCreateOrder() {
    Order order = new Order();
    List<Event> events = order.getEvents();
    assertEquals(1, events.size());
    Event event = events.get(0);
    assertTrue(event instanceof OrderPlacedEvent);
} 

NoSQL 12

 

当这种措施中,聚合根对天地事件之保留只能是现之,在针对该聚合根操作就之后,我们应当用世界事件公布出去并马上清空events集合。可以设想当持久化聚合根时开展如此的操作,在DDD中即为资源库(Repository):

 

NoSQL 13

public class OrderRepository {
    private EventPublisher eventPublisher;

    public void save(Order order) {
        //save the order
        //...
        List<Event> events = order.getEvents();
        events.forEach(event -> eventPublisher.publish(event));
        order.clearEvents();
    }
}

NoSQL 14

 

除此之外,还有一样种与“临时保存领域事件”相似之做法是“每当聚合根方法被直接回领域事件”,然后于Repository中展开披露。这种措施依旧时有发生酷好的可测性,并且开发人员不用手动清空先前底轩然大波集合,不过还是得记住在Repository中将事件发表出来。另外,这种艺术不切合创建聚合根的景,因为这时底创造进程既而返回聚合根本身,又如回来领域事件。

 

 这种方法为起坏的地方,比如其要求开发人员在历次换代聚合根时还必须记得清空events集合,忘记这样做将为次带来惨重的bug。不过尽管如此如此,这还是是笔者于推荐的方法。 

 

事情操作和波公布之原子性 

则于不同聚合根之间我们运用了基于领域事件的尾声一致性,但是以事情操作以及事件发布期间我们还要用大一致性,也就算这二者的发应是原子的,要么全部遂,要么全部败诉,否则最终一致性根本无从谈起。以上文中“订单积分”为例,如果客户下单成功,但是事件发送失败,下游的账户体系便将不顶事件,导致最后客户的积分并无多。 

 

若是确保工作操作以及波揭示期间的原子性,最直接的章程就是是采取XA事务,比如Java中之JTA,这种方法由于其重量级并无吃人们所主。但是,对于有些对性能要求不那么强之网,这种艺术未尝不是一个拣。一些开框架都会支持独立于应用服务器的XA事务管理器(如Atomikos 和Bitronix),比如Spring
Boot作为一个微服务框架便提供了针对Atomikos和Bitronix的支持。 

 

如若JTA不是你的选择项,那么得考虑使用事件表明底法。这种方式首先以事件保存到聚合根所当的数据库中,由于事件说明和聚合根表同属于一个数据库,整个经过仅待一个当地工作就会不辱使命。然后,在一个单身的后台任务中读取事件表明中不宣布的事件,再以事件公布暨消息中间件中。 

 

 

 

这种方法需要留意少只问题,第一独凡是由于发布了风波后要将表中的轩然大波标记成“已宣布”状态,即依然涉及到对数据库的操作,因此宣布事件及标志“已公布”之间用原子性。当然,此时照例可以使用XANoSQL事务,但是及时违背了应用事件表明底初衷。一栽缓解方式是将事件的花费方创建成幂等的,即消费方可以频繁花及一个事变。这个过程大概为:整个过程中事件发送和数据库更新采用各自的事务管理,此时发生或发生的状是事件发送成功而数据库更新失败,这样以生一致涂鸦事件公布操作着,由于原先宣布过的波在数据库被还是是“未宣布”状态,该事件将让还发布到信息网受,导致事件又,但由事件的消费方是幂等的,因此事件还无会见设有问题。 

 

另外一个要注意的问题是持久化机制的挑选。其实对DDD中之聚合根来说,NoSQL是比叫干项目数据库更适于的选择,比如用MongoDB的Document保存聚合根便是种植非常自然之主意。但是大部分NoSQL是匪支持ACID的,也就是说不可知担保聚合更新与波揭示期间的原子性。还吓,关系项目数据库也于通往NoSQL方向发展,比如新本子的PostgreSQL(版本9.4)和MySQL(版本5.7)已经会提供有NoSQL特征的JSON存储和因JSON的查询。此时,我们好考虑将聚合根序列化成JSON格式的数码开展封存,从而避免了使用重量级的ORM工具,又足以于差不多只数据里面保证ACID,何乐而不为? 

 

总结

世界事件要用以解耦微服务,此时逐一微服务之间以形成最终一致性。事件风暴活动促进我们本着微服务进行拆分,并且有助于我们深深摸底某圈子。领域事件作为曾经生了之史数据,在建模时该拿其创造为不可变的非常规值对象。存在多方用于发布领域事件,其中“在集结中即保存领域事件”的办法是值得尊重的。另外,我们得考虑到集结更新与事件发布期间的原子性,可以设想以XA事务或者使独立的风波说明。为了避免事件再度带来的问题,最好之法是以事件的消费方创建为幂等的。 

网站地图xml地图