分析https://github.com/eventuate-tram/eventuate-tram-sagas-examples-customers-and-orders中的例子.
例子中使用Eventuate Tram framework,spring boot ,jpa。实现的逻辑是上一篇文章讲到的电子商务网站的例子。
体系图
这个例子中有两个service。一个order service ,一个customer service。
关于order service,
- 首先为order建立一个domain对象,每一个order会存储一个OrderState的字段,OrderState有PENDING, APPROVED, REJECTED三种状态。Order的初始状态为PENDING。
package io.eventuate.examples.tram.ordersandcustomers.orders.domain; import io.eventuate.examples.tram.ordersandcustomers.commondomain.OrderCreatedEvent; import io.eventuate.examples.tram.ordersandcustomers.commondomain.OrderDetails; import io.eventuate.examples.tram.ordersandcustomers.commondomain.OrderState; import io.eventuate.tram.events.ResultWithEvents; import javax.persistence.*; import static java.util.Collections.singletonList; @Entity @Table(name="orders") @Access(AccessType.FIELD) public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private OrderState state; @Embedded private OrderDetails orderDetails; public Order() { } public Order(OrderDetails orderDetails) { this.orderDetails = orderDetails; this.state = OrderState.PENDING; } public static ResultWithEvents<Order> createOrder(OrderDetails orderDetails) { Order order = new Order(orderDetails); OrderCreatedEvent orderCreatedEvent = new OrderCreatedEvent(orderDetails); return new ResultWithEvents<>(order, singletonList(orderCreatedEvent)); } public Long getId() { return id; } public void noteCreditReserved() { this.state = OrderState.APPROVED; } public void noteCreditReservationFailed() { this.state = OrderState.REJECTED; } public OrderState getState() { return state; } public OrderDetails getOrderDetails() { return orderDetails; } }
- 当OrderService创建一个order时,会先创建一个ResultWithEvents对象,然后将order存储到数据库中,然后使用domainEventPublisher发布关于这个order的event。 可以看到createOrder方法上有@Transactional注解,说明
orderRepository.save(order); 和 domainEventPublisher.publish(Order.class, order.getId(), orderWithEvents.events); 这两行代码要在一个事务中,保证原子性,order一旦保存,orderCreatedEvent事件也必须也要进行持久化
@Transactional public Order createOrder(OrderDetails orderDetails) { ResultWithEvents<Order> orderWithEvents = Order.createOrder(orderDetails); Order order = orderWithEvents.result; orderRepository.save(order); domainEventPublisher.publish(Order.class, order.getId(), orderWithEvents.events); return order; }
public static ResultWithEvents<Order> createOrder(OrderDetails orderDetails) { Order order = new Order(orderDetails); OrderCreatedEvent orderCreatedEvent = new OrderCreatedEvent(orderDetails); return new ResultWithEvents<>(order, singletonList(orderCreatedEvent)); }
-
当order service发布了orderCreateEvent事件后,customer service会消费orderCreateEvent事件,customer service会先冻结 orderCreatedEvent事件中的订单金额,当冻结成功后,customer service会发布一个customerCreditReservedEvent,很明显,这个事件是用来通知order service已成功为用户冻结金额。
package io.eventuate.examples.tram.ordersandcustomers.customers.service; import io.eventuate.examples.tram.ordersandcustomers.commondomain.CustomerCreditReservationFailedEvent; import io.eventuate.examples.tram.ordersandcustomers.commondomain.CustomerCreditReservedEvent; import io.eventuate.examples.tram.ordersandcustomers.commondomain.OrderCreatedEvent; import io.eventuate.examples.tram.ordersandcustomers.customers.domain.Customer; import io.eventuate.examples.tram.ordersandcustomers.customers.domain.CustomerCreditLimitExceededException; import io.eventuate.examples.tram.ordersandcustomers.customers.domain.CustomerRepository; import io.eventuate.tram.events.publisher.DomainEventPublisher; import io.eventuate.tram.events.subscriber.DomainEventEnvelope; import io.eventuate.tram.events.subscriber.DomainEventHandlers; import io.eventuate.tram.events.subscriber.DomainEventHandlersBuilder; import org.springframework.beans.factory.annotation.Autowired; import java.util.Collections; public class OrderEventConsumer { @Autowired private CustomerRepository customerRepository; @Autowired private DomainEventPublisher domainEventPublisher; //订阅OrderCreatedEvent事件 public DomainEventHandlers domainEventHandlers() { return DomainEventHandlersBuilder .forAggregateType("io.eventuate.examples.tram.ordersandcustomers.orders.domain.Order") .onEvent(OrderCreatedEvent.class, this::orderCreatedEventHandler) .build(); } private void orderCreatedEventHandler(DomainEventEnvelope<OrderCreatedEvent> domainEventEnvelope) { Long orderId = Long.parseLong(domainEventEnvelope.getAggregateId()); OrderCreatedEvent orderCreatedEvent = domainEventEnvelope.getEvent(); Customer customer = customerRepository .findOne(orderCreatedEvent.getOrderDetails().getCustomerId()); try { //冻结用户的钱 customer.reserveCredit(orderId, orderCreatedEvent.getOrderDetails().getOrderTotal()); CustomerCreditReservedEvent customerCreditReservedEvent = new CustomerCreditReservedEvent(orderId, orderCreatedEvent.getOrderDetails()); //发布用户金额冻结成功的事件 domainEventPublisher.publish(Customer.class, customer.getId(), Collections.singletonList(customerCreditReservedEvent)); } catch (CustomerCreditLimitExceededException e) { //如果冻结失败,发布冻结失败事件 CustomerCreditReservationFailedEvent customerCreditReservationFailedEvent = new CustomerCreditReservationFailedEvent(orderId, orderCreatedEvent.getOrderDetails()); domainEventPublisher.publish(Customer.class, customer.getId(), Collections.singletonList(customerCreditReservationFailedEvent)); } } }
4.customer service发布完事件之后,会被order service消费掉
package io.eventuate.examples.tram.ordersandcustomers.orders.service;
import io.eventuate.examples.tram.ordersandcustomers.commondomain.CustomerCreditReservationFailedEvent;
import io.eventuate.examples.tram.ordersandcustomers.commondomain.CustomerCreditReservedEvent;
import io.eventuate.tram.events.subscriber.DomainEventEnvelope;
import io.eventuate.tram.events.subscriber.DomainEventHandlers;
import io.eventuate.tram.events.subscriber.DomainEventHandlersBuilder;
import org.springframework.beans.factory.annotation.Autowired;
public class CustomerEventConsumer {
@Autowired
private OrderService orderService;
//订阅CustomerCreditReservedEvent和CustomerCreditReservationFailedEvent事件
public DomainEventHandlers domainEventHandlers() {
return DomainEventHandlersBuilder
.forAggregateType("io.eventuate.examples.tram.ordersandcustomers.customers.domain.Customer")
.onEvent(CustomerCreditReservedEvent.class, this::customerCreditReservedEventHandler)
.onEvent(CustomerCreditReservationFailedEvent.class, this::customerCreditReservationFailedEventHandler)
.build();
}
private void customerCreditReservedEventHandler(DomainEventEnvelope<CustomerCreditReservedEvent> domainEventEnvelope) {
//用户金钱冻结成功,就修改订单状态为APPROVED
orderService.approveOrder(domainEventEnvelope.getEvent().getOrderId());
}
private void customerCreditReservationFailedEventHandler(DomainEventEnvelope<CustomerCreditReservationFailedEvent> domainEventEnvelope) {
//用户如果金钱不足,冻结失败,修改订单状态为REJECTED
orderService.rejectOrder(domainEventEnvelope.getEvent().getOrderId());
}
}
所以整体看来要实现Choreography-based saga模式还是挺复杂的,在借助与 Eventuate Tram framework框架之下,要为order 和customer创建域对象,新建order和orderCreatedEvent要保证原子性, Eventuate Tram framework会读取transaction log,然后发布事件到kafka中,order service和customer service订阅相应的事件,然后进行处理。order service要编写补偿事务代码。
总结一下,编写Choreography-based saga需要注意的几点:
- 编写带有状态字段的域对象
- 保证修改域对象的字段和发布相应的事件在同一个事务里
- 上游的service要编写补偿事务代码
- service订阅相应的事件进行处理。