Domain event publishing based on Spring Data

Keywords: Programming Database

Domain event publishing is a notification issued by a domain object to let other objects know that they have already processed an operation. Event publishing tries to decouple their own objects from external objects at the code level and reduce technical code intrusion.

1. Manually Publish Events

// Entity Definition
@Entity
public class Department implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer departmentId;

    @Enumerated(EnumType.STRING)
    private State state;
}

// Event Definition
public class DepartmentEvent {
    private Department department;
    private State state;
    public DepartmentEvent(Department department) {
        this.department = department;
        state = department.getState();
    }
}

// Domain Services
@Service
public class ApplicationService {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    @Autowired
    private DepartmentRepository departmentRepository;

    @Transactional(rollbackFor = Exception.class)
    public void departmentAdd(Department department) {
        departmentRepository.save(department);
        // Event Publishing
        applicationEventPublisher.publishEvent(new DepartmentEvent(department));
    }
}

Using applicationEventPublisher.publishEvent to publish realm events after realm service processing is complete requires that events be explicitly published in business code and the ApplicationEventPublisher class be introduced into the realm service, but it is invasive to the realm service itself, but flexible.

2. Automatically publish events

// Entity Definition
@Entity
public class SaleOrder implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer orderId;
   
    @Enumerated(EnumType.STRING)
    private State state;

    // Return Type Definition
    @DomainEvents
    public List<Object> domainEvents(){
        return Stream.of(new SaleOrderEvent(this)).collect(Collectors.toList());
    }

    // callback after event Publishing
    @AfterDomainEventPublication
    void callback() {
        System.err.println("ok");
    }
}

// Event Definition
public class SaleOrderEvent {
    private SaleOrder saleOrder;
    private State state;
    public SaleOrderEvent(SaleOrder saleOrder) {
        this.saleOrder = saleOrder;
        state = saleOrder.getState();
    }
}

// Domain Services
@Service
public class ApplicationService {
    @Autowired
    private OrderRepository orderRepository;
    
    @Transactional(rollbackFor = Exception.class)
    public void saleOrderAdd(SaleOrder saleOrder) {
        orderRepository.save(saleOrder);
    }
}

Defining the type of event return using @DomainEvents must be a collection and using @AfterDomainEventPublication to define callbacks after the event is published. This method real event type is defined in the entity and is completely decoupled from the realm service without intrusion.Event publishing is automatically invoked after orderRepository.save(saleOrder), while event publishing is not invoked by the delete method.

3. Event Monitoring

@Component
public class ApplicationEventProcessor {

    @EventListener(condition = "#departmentEvent.getState().toString() == 'SUCCEED'")
    public void departmentCreated(DepartmentEvent departmentEvent) {
        System.err.println("dept-event1:" + departmentEvent);
    }

    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, condition = "#saleOrderEvent.getState().toString() == 'SUCCEED'")
    public void saleOrderCreated(SaleOrderEvent saleOrderEvent) {
        System.err.println("sale-event succeed1:" + saleOrderEvent);
    }

    @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT, condition = "#saleOrderEvent.getState().toString() == 'SUCCEED'")
    public void saleOrderCreatedBefore(SaleOrderEvent saleOrderEvent) {
        System.err.println("sale-event succeed2:" + saleOrderEvent);
    }

    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void saleOrderCreatedFailed(SaleOrderEvent saleOrderEvent) {
        System.out.println("sale-event failed:" + saleOrderEvent);
    }
}

1. Use @EventListener to listen for events

@EventListener has no transaction support and can be monitored as long as the event is sent

@Transactional(rollbackFor = Exception.class)
public void departmentAdd(Department department) {
    departmentRepository.save(department);
    applicationEventPublisher.publishEvent(new DepartmentEvent(department));
    throw new RuntimeException("failed");
}

This can cause a transaction to fail and roll back, but the event monitoring side has been executed, possibly resulting in inconsistent data

2. Use @TransactionalEventListener to listen for events

  • TransactionPhase.BEFORE_COMMIT transaction before commit
  • After the TransactionPhase.AFTER_COMMIT transaction is committed
  • After the TransactionPhase.AFTER_ROLLBACK transaction is rolled back
  • TransactionPhase.AFTER_COMPLETION transaction completed

TransactionPhase.AFTER_COMMIT enables event monitoring methods to be executed after a transaction has completed, thereby ensuring data consistency

3. TransactionPhase.AFTER_ROLLBACK Rollback Transaction Issue

@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK, condition = "#departmentEvent.getState().toString() == 'SUCCEED'")
public void departmentCreatedFailed(DepartmentEvent departmentEvent) {
    System.err.println("dept-event3:" + departmentEvent);
}

Because @DomainEvents work on entities and events are sent only after the successful execution of orderRepository.save(saleOrder), the AFTER_ROLLBACK method will only execute if other statements in the same transaction fail to execute or if the save method fails, rollback events will not be listened for.

4. @Async Async Event Listening

  • Without this comment, the event listening method is a transaction with the main method.
  • Using this comment will break away from the original transaction and BEFORE_COMMIT will not intercept the moment before the transaction commits
  • This note needs to be used with @EnableAsync

4. Summary

By using @DomainEvents, @TransactionalEventListener, we can effectively resolve the issue of domain events, reduce the invasion of business code, and do our best to solve the problem of data consistency. In a distributed structure, event notifications are sent to other services through MQ. To solve the consistency problem and prevent the other service from processing failure, events can be persisted to the database before retrying.

V. Source Code

https://gitee.com/hypier/barry-jpa/tree/master/jpa-section-5

Posted by kid_c on Thu, 02 Apr 2020 01:07:09 -0700