Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save alifhaikal88/d8b151dd7f767e9f858163ea635479e9 to your computer and use it in GitHub Desktop.

Select an option

Save alifhaikal88/d8b151dd7f767e9f858163ea635479e9 to your computer and use it in GitHub Desktop.
PostSalesTransactionTrigger Deadlock Prevention Refactoring - Evolution from RabbitMQ Queues to Spring Events

PostSalesTransactionTrigger Deadlock Prevention Refactoring

Overview

Original Commit: 89f31423ade0df79ceee6bc75a33ab23cda1e818
Final Implementation: Spring Event-driven Architecture
Author: Alif Haikal [email protected]
Date: Wed Jul 30 2025

This document provides a comprehensive analysis of the refactoring implemented to prevent deadlocks in the PostSalesTransactionTrigger class. The solution evolved from an initial RabbitMQ queue-based approach to a final Spring Event-driven architecture that successfully resolves both deadlock issues and transaction persistence problems.

Problem Statement

The original implementation of PostSalesTransactionTrigger was performing cost calculations synchronously within the main transaction flow, which could lead to:

  1. Database deadlocks when multiple transactions attempt to calculate costs simultaneously
  2. Long-running transactions that hold database locks for extended periods
  3. Performance bottlenecks during high transaction volumes
  4. Potential transaction failures due to lock timeouts

Solution Evolution

The refactoring went through two phases to arrive at the optimal solution:

Phase 1: RabbitMQ Queue-Based Architecture (Initial Implementation)

  • Approach: Asynchronous message queues for cost calculations
  • Issue Discovered: Transaction persistence problems due to context isolation
  • Status: Replaced with Spring Events

Phase 2: Spring Event-Driven Architecture (Final Implementation)

  • Approach: Spring Events with proper transaction management
  • Benefits: Same JVM context, proper transaction boundaries, lazy loading support
  • Status: Production-ready solution

Final Architecture Components

  1. Event Publisher: PostSalesTransactionTrigger publishes cost calculation events
  2. Event Handler: PostTransactionCostCalculationEventHandler processes calculations asynchronously
  3. Event Infrastructure: Spring's built-in event system with transaction support
  4. Transaction Management: @TransactionalEventListener with proper propagation

Changes Made

1. PostSalesTransactionTrigger.java

Location: src/main/java/my/axaipay/backend/modules/payment/job/PostSalesTransactionTrigger.java

Major Structural Changes

  • Removed direct cost calculations from the main transaction flow
  • Added Spring Event integration via ApplicationEventPublisher
  • Extracted methods for better code organization and maintainability
  • Removed RabbitMQ dependencies in favor of Spring Events

New Dependencies

@Autowired
private ApplicationEventPublisher eventPublisher;

Method Refactoring

Before: Synchronous cost calculations in main flow

// Old synchronous approach
try {
    logger.info("3. CALCULATE ACQUIRER COST");
    salesTransactionService.calculateAcquirerCost(salesTransaction);
} catch (ServiceException e) {
    e.printStackTrace();
}

try {
    logger.info("4. CALCULATE SALES PARTNER COMMISSION");
    salesTransactionService.calculateSalesPartnerCommission(salesTransaction);
} catch (ServiceException e) {
    logger.error("ERR_SP_001 Failed to calculate sales partner commission");
    e.printStackTrace();
}

After: Asynchronous event publishing

// New event-driven approach
logger.info("3. PUBLISH COST CALCULATION EVENT");
publishCostCalculationEvent(salesTransaction.getId(), transactionTime);

New Methods Added

  1. handlePaidTransaction(): Orchestrates all paid transaction processing
  2. sendTransactionEmails(): Handles email notifications separately
  3. updateTransactionSummary(): Manages summary updates
  4. publishCostCalculationEvent(): Publishes Spring Events for cost calculations

2. Spring Event Components (New Files)

PostTransactionCostCalculationEvent.java

Location: src/main/java/my/axaipay/backend/modules/payment/event/PostTransactionCostCalculationEvent.java

Event class that carries transaction data for cost calculations.

public class PostTransactionCostCalculationEvent extends ApplicationEvent {
    private final Long salesTransactionId;
    private final Date transactionTime;
    
    // Constructor and getters
}

PostTransactionCostCalculationEventHandler.java

Location: src/main/java/my/axaipay/backend/modules/payment/event/PostTransactionCostCalculationEventHandler.java

Event handler that processes cost calculations asynchronously with proper transaction management.

Key Features

  • Transactional Event Listener: Uses @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
  • Transaction Safety: Annotated with @Transactional(propagation = Propagation.REQUIRES_NEW)
  • Error Handling: Comprehensive exception handling with detailed logging
  • Lazy Loading Support: Proper transaction context prevents lazy loading issues
  • Input Validation: Validates transaction data before processing

Event Listener

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void handleCostCalculation(PostTransactionCostCalculationEvent event) {
    // Process both acquirer and sales partner cost calculations
}

3. Configuration Cleanup

RabbitMQQueueInitializer.java

Location: src/main/java/my/axaipay/backend/init/RabbitMQQueueInitializer.java

Changes Made: Removed queue-related configurations that were added in the initial RabbitMQ implementation.

application.properties

Location: src/main/resources/application.properties

Changes Made: Removed queue configuration properties that were no longer needed with the Spring Event approach.

Technical Benefits

1. Deadlock Prevention

  • Isolated Transactions: Cost calculations run in separate transactions with REQUIRES_NEW propagation
  • Reduced Lock Duration: Main transaction completes faster
  • No Cross-Transaction Dependencies: Each calculation is independent

2. Improved Performance

  • Non-blocking Operations: Main transaction flow doesn't wait for cost calculations
  • Same JVM Processing: No network overhead unlike queue-based approach
  • Better Resource Utilization: Database connections efficiently managed within application context

3. Enhanced Reliability

  • Transaction Persistence: Spring Events run in proper transaction context ensuring persistence
  • Lazy Loading Support: @TransactionalEventListener provides proper Hibernate session context
  • Error Isolation: Calculation errors are contained and logged with detailed context
  • Guaranteed Execution: Events processed within same JVM without external dependencies

4. Better Maintainability

  • Separation of Concerns: Cost calculations isolated in dedicated event handler
  • Cleaner Code Structure: Main trigger focuses on core transaction logic
  • Easier Testing: Components can be tested with real database without external message broker
  • Simpler Architecture: No external message broker dependency

5. Solved Issues from Queue Approach

  • Transaction Persistence: Events maintain proper transaction boundaries
  • Lazy Loading: Transaction context prevents LazyInitializationException
  • Connection Management: Uses application's database connection pool
  • Error Handling: Better exception handling with transaction rollback support

Event Flow

sequenceDiagram
    participant ST as SalesTransaction
    participant PST as PostSalesTransactionTrigger
    participant ES as SpringEventSystem
    participant EH as EventHandler
    participant STS as SalesTransactionService

    ST->>PST: Transaction becomes PAID
    PST->>PST: handlePaidTransaction()
    PST->>ES: Publish CostCalculationEvent
    PST->>PST: Continue with emails, summary, etc.
    PST->>PST: Commit main transaction
    
    Note over ES,EH: After main transaction commits
    ES->>EH: Deliver event (AFTER_COMMIT)
    EH->>EH: Start new transaction (REQUIRES_NEW)
    EH->>STS: calculateAcquirerCost()
    EH->>STS: calculateSalesPartnerCommission()
    EH->>EH: Commit cost calculation transaction
Loading

Error Handling Strategy

Publisher Side (PostSalesTransactionTrigger)

  • Non-blocking Failures: If event publishing fails, it's logged but doesn't affect main flow
  • Transaction Continuity: Main transaction continues even if event publishing fails
  • Simple Error Handling: Events are published synchronously within same JVM

Event Handler Side (PostTransactionCostCalculationEventHandler)

  • Transaction Rollback: Uses @Transactional(propagation = Propagation.REQUIRES_NEW)
  • Comprehensive Exception Handling: Separate handling for ServiceException, NullPointerException, and general exceptions
  • Input Validation: Validates transaction existence and required fields before processing
  • Detailed Logging: Context-aware error messages with transaction details
  • Graceful Degradation: Missing or invalid transactions are handled without crashes
  • Lazy Loading Protection: Transaction context prevents Hibernate lazy loading issues

Specific Error Scenarios Handled

  1. NullPointerException: Common with missing MerchantSetting or PaymentMethodSetting data
  2. Lazy Loading Issues: Resolved with proper transaction context in event handler
  3. Missing Transaction Data: Early validation prevents processing incomplete transactions
  4. Service Layer Exceptions: Proper error codes and logging for different failure types

Testing Additions

PostTransactionCostCalculationEventIntegrationTest.java

Location: src/test/java/my/axaipay/backend/modules/payment/event/PostTransactionCostCalculationEventIntegrationTest.java

Features:

  • Real Database Integration: Tests with actual transaction data, no mocking
  • Event Flow Testing: Validates complete event publishing and handling flow
  • Bulk Processing: Tests multiple transactions from external file input
  • Error Handling: Tests graceful handling of non-existent transactions
  • Transaction Persistence: Verifies that cost calculations are actually persisted
  • Event Properties: Validates event object creation and data integrity

Test Methods:

  1. testSingleTransactionCostCalculation() - Single transaction end-to-end test
  2. testBulkTransactionCostCalculation() - Bulk processing from file input
  3. testDirectEventHandlerCall() - Direct method invocation testing
  4. testNonExistentTransaction() - Error handling validation
  5. testEventProperties() - Event object validation

Benefits:

  • No external dependencies (RabbitMQ) required for testing
  • Real database validation ensures persistence works correctly
  • Comprehensive error scenario coverage
  • Support for bulk testing with transaction IDs from external files

Configuration Requirements

Spring Event Setup

No additional configuration required - Spring Events work out of the box with Spring Boot.

Application Properties

No additional properties needed for Spring Events.

Required Annotations

Ensure the following annotations are properly configured:

  1. Event Handler: @Component for Spring to detect the event handler
  2. Event Listener: @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
  3. Transaction Management: @Transactional(propagation = Propagation.REQUIRES_NEW)

Migration Considerations

1. Deployment Strategy

  • Zero-downtime deployment possible with Spring Events (no external dependencies)
  • No external setup required (no RabbitMQ queues to configure)
  • Immediate availability after deployment as events work within same JVM

2. Monitoring

  • Application logs to monitor event processing success/failure rates
  • Transaction metrics to track cost calculation performance
  • Database monitoring to ensure calculations are being persisted
  • Error rate monitoring through application logging framework

3. Rollback Plan

  • Simple rollback as Spring Events have no external dependencies
  • Database consistency maintained through proper transaction boundaries
  • No cleanup required of external message brokers or queues

4. Benefits Over Queue Approach

  • Simplified deployment - no external message broker configuration
  • Better reliability - no network calls or message broker failures
  • Easier troubleshooting - all processing within same application logs
  • Lower latency - no serialization/deserialization or network overhead

Conclusion

This refactoring successfully addresses the deadlock issues in the PostSalesTransactionTrigger by:

  1. Decoupling cost calculations from the main transaction flow
  2. Implementing asynchronous processing via Spring Events
  3. Improving system reliability and performance
  4. Maintaining data consistency through proper transaction handling
  5. Enhancing code maintainability through better separation of concerns
  6. Solving transaction persistence issues that occurred with the queue-based approach

The final Spring Event-driven solution provides a robust, maintainable approach to handling post-transaction processing while:

  • Preventing database deadlocks from the original synchronous implementation
  • Solving transaction persistence issues from the queue-based approach
  • Providing proper lazy loading support through transaction context
  • Eliminating external dependencies and complexity

Key Success Factors

  1. TransactionalEventListener: Ensures events run after main transaction commits
  2. REQUIRES_NEW Propagation: Creates independent transactions for cost calculations
  3. Same JVM Context: Maintains proper Hibernate session and connection management
  4. Comprehensive Error Handling: Gracefully handles various failure scenarios
  5. Real Database Testing: Integration tests validate actual persistence behavior

The solution successfully balances performance, reliability, and maintainability while eliminating the original deadlock problems.

Files Modified (Final Implementation)

File Changes Lines Added Lines Removed
PostSalesTransactionTrigger.java Refactored to use Spring Events +95 -67
PostTransactionCostCalculationEvent.java New event class +28 0
PostTransactionCostCalculationEventHandler.java New event handler +102 0
RabbitMQQueueInitializer.java Removed queue declarations 0 -7
application.properties Removed queue configurations 0 -2
PostTransactionCostCalculationEventIntegrationTest.java New comprehensive test +233 0

Removed Files:

  • PostTransactionCostCalculationQueueConsumer.java (replaced with event handler)
  • PostTransactionCostCalculationQueueConsumerTest.java (replaced with integration test)

Total: +458 lines added, -76 lines removed

Evolution Summary

  1. Phase 1 (Queue Implementation): +592 lines, -59 lines
  2. Phase 2 (Spring Events): +458 lines, -76 lines
  3. Net Result: Cleaner, more maintainable solution with fewer lines of code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment