微服务 Choreography-based saga example

分析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,

  1. 首先为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;
      }
    }
    

     

  2. 当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));
      }

     

  3. 当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需要注意的几点:

  1. 编写带有状态字段的域对象
  2. 保证修改域对象的字段和发布相应的事件在同一个事务里
  3. 上游的service要编写补偿事务代码
  4. service订阅相应的事件进行处理。

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值