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