# Transaction Management Guide

## Overview

This document describes the transaction management strategy for the B2B Flight Ticketing Platform.

**Requirements:** 13.2

## Transaction Configuration

The platform uses Spring's declarative transaction management with the following configuration:

- **Transaction Manager:** JpaTransactionManager
- **Isolation Level:** Default (READ_COMMITTED)
- **Propagation:** REQUIRED (default)
- **Rollback:** Automatic on all exceptions (including checked exceptions)

## Transaction Boundaries

### Service Layer Transactions

All service methods that modify data should be annotated with `@Transactional`:

```java
@Service
@Transactional
public class WalletServiceImpl implements WalletService {
    
    @Override
    public TransactionResult debitWallet(UUID agencyId, BigDecimal amount, String reference) {
        // All database operations within this method are part of a single transaction
        // If any exception occurs, all changes are rolled back
    }
}
```

### Read-Only Transactions

For read-only operations, use `@Transactional(readOnly = true)` for performance optimization:

```java
@Override
@Transactional(readOnly = true)
public Wallet getWalletByAgency(UUID agencyId) {
    return walletRepository.findByAgencyId(agencyId)
        .orElseThrow(() -> new WalletNotFoundException("Wallet not found"));
}
```

## Rollback Behavior

### Automatic Rollback

Transactions automatically roll back on:
- All `RuntimeException` and its subclasses
- All checked exceptions (configured via `rollbackFor`)
- Database constraint violations
- Connection failures

### Example: Atomic Wallet Debit

```java
@Override
@Transactional
public TransactionResult debitWallet(UUID agencyId, BigDecimal amount, String reference) {
    // Step 1: Get wallet (with pessimistic lock)
    Wallet wallet = walletRepository.findByAgencyIdForUpdate(agencyId)
        .orElseThrow(() -> new WalletNotFoundException("Wallet not found"));
    
    // Step 2: Validate balance
    if (wallet.getAvailableBalance().compareTo(amount) < 0) {
        throw new InsufficientBalanceException("Insufficient balance");
        // Transaction rolls back automatically
    }
    
    // Step 3: Update wallet
    BigDecimal balanceBefore = wallet.getBalance();
    wallet.setBalance(wallet.getBalance().subtract(amount));
    walletRepository.save(wallet);
    
    // Step 4: Create transaction record
    WalletTransaction transaction = new WalletTransaction();
    transaction.setWalletId(wallet.getId());
    transaction.setAmount(amount);
    transaction.setBalanceBefore(balanceBefore);
    transaction.setBalanceAfter(wallet.getBalance());
    transactionRepository.save(transaction);
    
    // If any step fails, ALL changes are rolled back
    return TransactionResult.success(transaction.getId());
}
```

## Transaction Logging

The `TransactionLoggingAspect` automatically logs:

- Transaction start
- Transaction commit (with execution time)
- Transaction rollback (with error details)
- Slow transactions (> 1 second)

Example log output:

```
DEBUG - Starting transaction for method: WalletServiceImpl.debitWallet(..)
DEBUG - Transaction committed successfully for method: WalletServiceImpl.debitWallet(..) (45ms)
```

On failure:

```
ERROR - Transaction rolled back for method: WalletServiceImpl.debitWallet(..) (23ms) - Error: Insufficient balance
```

## Compensating Transactions

For operations that span multiple systems (e.g., Amadeus API + Database), implement compensating transactions:

### Example: Ticket Issuance with Compensation

```java
@Override
@Transactional
public Ticket issueTicket(UUID bookingId, UUID issuedBy) {
    Booking booking = getBooking(bookingId);
    
    // Step 1: Debit wallet (database transaction)
    TransactionResult walletResult = walletService.debitWallet(
        booking.getAgencyId(), 
        booking.getTotalPrice(), 
        "TICKET-" + bookingId
    );
    
    try {
        // Step 2: Issue ticket in Amadeus (external system)
        AmadeusTicketResponse amadeusResponse = amadeusClient.issueTicket(
            createTicketRequest(booking)
        );
        
        // Step 3: Create ticket record (database transaction)
        Ticket ticket = createTicketRecord(booking, amadeusResponse, walletResult.getTransactionId());
        
        return ticket;
        
    } catch (AmadeusServiceException e) {
        // Compensating transaction: Refund wallet
        log.error("Ticket issuance failed in Amadeus, refunding wallet", e);
        
        walletService.creditWallet(
            booking.getAgencyId(),
            booking.getTotalPrice(),
            "REFUND-" + bookingId
        );
        
        // Alert administrators
        sendCriticalAlert("Ticket issuance failed after payment", e);
        
        throw new TransactionFailureException("Ticket issuance failed", e);
    }
}
```

## Pessimistic Locking

For concurrent access to critical resources (e.g., wallet balance), use pessimistic locking:

```java
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT w FROM Wallet w WHERE w.agencyId = :agencyId")
Optional<Wallet> findByAgencyIdForUpdate(@Param("agencyId") UUID agencyId);
```

This ensures that only one transaction can modify the wallet at a time.

## Transaction Isolation

The default isolation level is `READ_COMMITTED`, which prevents:
- Dirty reads
- Lost updates

For operations requiring stricter isolation, use:

```java
@Transactional(isolation = Isolation.SERIALIZABLE)
public void criticalOperation() {
    // Highest isolation level
    // Prevents phantom reads
}
```

## Best Practices

### DO:
✅ Keep transactions short and focused
✅ Use `@Transactional(readOnly = true)` for read operations
✅ Handle exceptions explicitly
✅ Use pessimistic locking for concurrent updates
✅ Implement compensating transactions for multi-system operations
✅ Log transaction failures with context

### DON'T:
❌ Call external APIs within transactions (unless compensating)
❌ Perform long-running operations in transactions
❌ Catch exceptions without re-throwing (prevents rollback)
❌ Use `@Transactional` on private methods (won't work)
❌ Nest transactions unnecessarily

## Monitoring

Transaction metrics are exposed via Spring Boot Actuator:

- `/actuator/metrics/spring.data.repository.invocations`
- `/actuator/metrics/hikaricp.connections.active`
- `/actuator/metrics/hikaricp.connections.timeout`

Monitor these metrics to detect:
- Long-running transactions
- Connection pool exhaustion
- Transaction failures

## Error Handling

All transaction failures are handled by the `GlobalExceptionHandler`:

- Database errors → HTTP 500 with trace ID
- Transaction failures → HTTP 500 with rollback confirmation
- Critical errors → Alert sent to Super_Admin

See `GlobalExceptionHandler.java` for implementation details.

## Testing

Test transaction rollback behavior:

```java
@Test
void shouldRollbackOnInsufficientBalance() {
    // Given
    Wallet wallet = createWalletWithBalance(100);
    
    // When & Then
    assertThatThrownBy(() -> 
        walletService.debitWallet(wallet.getAgencyId(), BigDecimal.valueOf(150), "test")
    ).isInstanceOf(InsufficientBalanceException.class);
    
    // Verify rollback
    Wallet updatedWallet = walletRepository.findById(wallet.getId()).orElseThrow();
    assertThat(updatedWallet.getBalance()).isEqualTo(BigDecimal.valueOf(100));
}
```

## References

- Spring Transaction Management: https://docs.spring.io/spring-framework/reference/data-access/transaction.html
- JPA Locking: https://docs.oracle.com/javaee/7/tutorial/persistence-locking.htm
- ACID Properties: https://en.wikipedia.org/wiki/ACID
