分析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,
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 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;
}
}
orderRepository.save(order);
和
domainEventPublisher.publish(Order.class, order.getId(), orderWithEvents.events); 这两行代码要在一个事务中,保证原子性,order一旦保存,orderCreatedEvent事件也必须也要进行持久化
@Transactional
public Order createOrder(OrderDetails orderDetails) {
ResultWithEvents orderWithEvents = Order.createOrder(orderDetails);
Order order = orderWithEvents.result;
orderRepository.save(order);
domainEventPublisher.publish(Order.class, order.getId(), orderWithEvents.events);
return order;
}
public static ResultWithEvents 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 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 domainEventEnvelope) {
//用户金钱冻结成功,就修改订单状态为APPROVED
orderService.approveOrder(domainEventEnvelope.getEvent().getOrderId());
}
private void customerCreditReservationFailedEventHandler(DomainEventEnvelope 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需要注意的几点: