Skip to content

Conversation

NFUChen
Copy link
Contributor

@NFUChen NFUChen commented Jun 19, 2025

🚀 Implement Context-based Transactional Session Management with Depth Tracking

📋 Overview

This PR introduces a robust, nested-safe transactional session management system for PySpringModel with explicit session depth tracking. The implementation replaces manual session handling with a context-aware approach that automatically manages database sessions and transactions across the entire application stack, ensuring proper transaction boundaries through depth monitoring.

🎯 Problem Statement

Previously, the codebase had several session management issues:

  • Session Proliferation: Each repository/service method created its own database session
  • No Transaction Boundaries: Operations weren't properly grouped into atomic transactions
  • Resource Leaks: Sessions could remain open if not properly managed in with blocks
  • No Nested Transaction Support: Multiple repository calls couldn't share the same transaction
  • Inconsistent Patterns: Mix of create_session() and create_managed_session() usage
  • Complex Nested Logic: No reliable way to determine transaction hierarchy and ensure only outermost transactions commit/rollback

🛠️ Solution

Implemented a Context Variable-based Session Management system with explicit depth tracking featuring the following components:

🔧 Core Components

1. SessionContextHolder with Depth Tracking (py_spring_model/core/session_context_holder.py)

  • Thread-safe session storage using Python's contextvars
  • Session Depth Counter: Tracks nested transaction levels using _session_depth ContextVar
  • Provides get_or_create_session(), has_session(), and clear_session() methods
  • Depth Management Methods:
    • get_session_depth(): Returns current nesting level
    • enter_session(): Increments depth and returns new level
    • exit_session(): Decrements depth and cleans up session at level 0
  • Prevents Negative Depth: Ensures depth counter never goes below 0
  • Ensures each request/context has its own isolated session with proper depth tracking

2. @transactional Decorator with Depth Awareness

  • Depth-Based Commit Control: Only commits/rollbacks at outermost level (depth == 1)
  • Nested-Safe: Inner @Transactional methods reuse the outer transaction's session
  • Automatic Session Lifecycle: Manages enter/exit session depth automatically
  • Exception Safety: Any exception triggers rollback only at the outermost transaction
  • Session Cleanup: Session is closed only when depth reaches 0

3. TransactionalDepth Enum

class TransactionalDepth(IntEnum):
    OUTERMOST = 1    # Outermost transaction level
    ON_EXIT = 0      # Session cleanup level

4. Session Middleware (py_spring_model/py_spring_model_rest/controller/session_controller.py)

  • HTTP middleware that guarantees session cleanup after each request
  • Prevents session leakage between HTTP requests
  • Works as a safety net for any unhandled session scenarios
  • Resets depth counter to prevent cross-request contamination

🔄 Key Changes

Repository Layer (py_spring_model/repository/crud_repository.py)

  • Before: Each method created its own session with with self.create_managed_session()
  • After: Methods use SessionContextHolder.get_or_create_session() and @Transactional decorator
  • ✅ Removed manual session management from all CRUD operations
  • ✅ Methods now participate in broader transaction contexts with depth awareness

Service Layer (py_spring_model/py_spring_model_rest/service/)

  • PySpringModelRestService: All methods now use @Transactional decorator
  • CrudRepositoryImplementationService: Query execution uses context sessions
  • ✅ Eliminated with PySpringModel.create_session() patterns
  • ✅ Service methods automatically participate in nested transaction hierarchies

Module Exports (py_spring_model/__init__.py)

  • ✅ Added SessionContextHolder to public API for depth management access
  • ✅ Added SessionController to REST controller registration

🎁 Benefits

🔒 Transaction Safety with Depth Control

@Transactional  # Depth: 1 (OUTERMOST)
def complex_business_operation():
    user_repo.save(user)           # Depth: 2, reuses session
    account_repo.save(account)     # Depth: 3, reuses session  
    audit_repo.save(audit_log)     # Depth: 4, reuses session
    # Only this method commits/rollbacks (depth == 1)

🎯 Smart Nested Transaction Support

@Transactional  # Depth: 1 (commits here)
def outer_operation():
    create_user()      # Depth: 2, no commit
    update_balance()   # Depth: 2, no commit
    
@Transactional  # Depth: 2 when called from outer_operation
def create_user():
    # This doesn't commit independently
    user_repo.save(User(...))  # Depth: 3

📊 Depth-Aware Transaction Monitoring

# Debug transaction hierarchy
def get_transaction_info():
    depth = SessionContextHolder.get_session_depth()
    has_session = SessionContextHolder.has_session()
    return f"Depth: {depth}, Active: {has_session}"

🧹 Automatic Resource Management

  • Sessions are automatically cleaned up when depth reaches 0
  • No more manual with session blocks needed
  • Prevents session accumulation and memory leaks
  • Depth counter prevents premature session closure

🚀 Performance Improvements

  • Reduced Session Creation: Multiple operations share the same session across depths
  • Connection Pool Efficiency: Single connection per transaction hierarchy
  • Reduced Transaction Overhead: Batch operations in single transactions
  • Intelligent Commit Strategy: Only outermost transactions commit

🧪 Testing Updates

Core Session Depth Tests (tests/test_session_depth.py)

  • Depth Counter Validation: Tests increment/decrement behavior
  • Session Lifecycle: Verifies session cleanup only at depth 0
  • Nested Transaction Testing: Parametrized tests for 1-5 nesting levels
  • Edge Case Handling: Prevents negative depth values
  • Clear Session Behavior: Verifies depth reset functionality

Enhanced Transactional Tests (tests/test_transactional_decorator.py)

  • Depth-Aware Commit/Rollback: Tests proper depth-based transaction control
  • Session Sharing Verification: Confirms same session across nested calls
  • Exception Handling: Tests rollback behavior at various depths
  • Context Isolation: Ensures proper session separation between operations

Repository & Service Tests

  • tests/test_crud_repository.py
  • tests/test_crud_repository_implementation_service.py
  • tests/test_query_modifying_operations.py

🔄 Migration Guide

For Application Code:

# ❌ Old Pattern  
def some_business_logic():
    with PySpringModel.create_session() as session:
        # database operations
        session.commit()

# ✅ New Pattern with Depth Awareness
@Transactional  # Depth: 1, will commit
def some_business_logic():
    # database operations
    # commit/rollback handled automatically based on depth

For Repository Usage:

# ❌ Old Pattern
def complex_operation():
    user = user_repo.save(user)      # Creates own session
    account = account_repo.save(account)  # Creates own session  
    # Each operation commits separately

# ✅ New Pattern with Shared Session Depth
@Transactional  # Depth: 1 (outermost)
def complex_operation():
    user = user_repo.save(user)      # Depth: 2, shares session
    account = account_repo.save(account)  # Depth: 3, shares session
    # All operations commit together at depth 1

For Debugging Transaction Hierarchy:

@Transactional
def debug_transaction_flow():
    print(f"Current depth: {SessionContextHolder.get_session_depth()}")
    nested_operation()  # Will show depth + 1
    
@Transactional
def nested_operation():
    print(f"Nested depth: {SessionContextHolder.get_session_depth()}")

⚠️ Breaking Changes

  • Repository Method Signatures: Some internal methods no longer accept session parameters
  • Manual Session Management: Code relying on manual with session blocks may need updates
  • Transaction Boundaries: Operations now participate in broader transaction contexts
  • Depth Tracking: New depth-based commit/rollback logic may affect custom transaction handling

🔍 Backwards Compatibility

  • Public Repository API: No changes to public repository methods (save(), find_by_id(), etc.)
  • Service Layer API: REST endpoints continue to work unchanged
  • Model Definitions: No changes required to existing PySpringModel classes
  • Session Access: Manual session access still available via SessionContextHolder

🧪 How to Test

  1. Unit Tests: Run existing test suite - all tests should pass
  2. Depth Testing: Test various nesting levels (1-5 deep)
  3. Integration Tests: Test nested transaction scenarios
  4. Performance Tests: Verify reduced session creation and improved throughput
  5. Memory Tests: Confirm no session leaks over extended periods
  6. Depth Edge Cases: Test depth counter edge cases and error scenarios

📚 Usage Examples

Simple Transaction

@Transactional  # Depth: 1
def create_user_with_profile(user_data, profile_data):
    user = user_repository.save(User(**user_data))      # Depth: 2
    profile = profile_repository.save(Profile(user_id=user.id, **profile_data))  # Depth: 3
    return user, profile  # Commits at depth 1

Complex Nested Transactions

@Transactional  # Depth: 1 (outermost - commits here)
def process_order(order_data):
    order = create_order(order_data)     # Depth: 2
    process_payment(order.total)         # Depth: 2
    update_inventory(order.items)        # Depth: 2
    send_confirmation(order.user_id)     # Depth: 2

@Transactional  # Depth: 2 when called from process_order
def create_order(order_data):
    return order_repository.save(Order(**order_data))  # Depth: 3

@Transactional  # Depth: 2 when called from process_order
def process_payment(amount):
    payment = payment_service.charge(amount)  # Depth: 3
    audit_service.log_payment(payment.id)     # Depth: 3

Depth-Aware Error Handling

@Transactional  # Depth: 1
def safe_bulk_operation():
    try:
        for item in items:
            process_item(item)  # Each call increases depth
        # Success: commits at depth 1
    except Exception as e:
        # Error: rollback at depth 1 affects all nested operations
        logger.error(f"Bulk operation failed at depth {SessionContextHolder.get_session_depth()}: {e}")
        raise

🔧 Session Depth Architecture

Depth Management Flow

HTTP Request
├── SessionController.session_middleware()
│   ├── Depth: 0 (initial)
│   └── @Transactional method_1()
│       ├── Depth: 1 (OUTERMOST - commits/rollbacks)
│       └── @Transactional method_2()
│           ├── Depth: 2 (shares session)
│           └── @Transactional method_3()
│               ├── Depth: 3 (shares session)
│               └── ...
├── Response returned
└── Session cleaned up (depth reset to 0)

Session Lifecycle with Depth

  1. Depth 0: No active session
  2. Depth 1: Session created, outermost transaction (commits/rollbacks)
  3. Depth 2+: Session reused, nested transactions (no commit/rollback)
  4. Back to 0: Session closed, depth counter reset

This implementation provides a solid foundation for reliable, performant database transaction management with intelligent depth tracking, ensuring proper transaction boundaries while maintaining clean, readable code patterns. The explicit depth management eliminates guesswork about transaction hierarchy and provides robust nested transaction support. 🎉

…rations

- Introduced `SessionContextHolder` to manage SQLAlchemy sessions using context variables.
- Added `Transactional` decorator to ensure session commits and rollbacks during CRUD operations.
- Refactored `CrudRepository` methods to utilize the new session management, enhancing code clarity and reducing session handling boilerplate.
- Updated tests to reflect changes in the repository methods, ensuring proper functionality without session parameters.
…Management

- Updated CRUD methods in `PySpringModelRestService` and `CrudRepositoryImplementationService` to use `SessionContextHolder` for session handling, replacing the previous context manager approach.
- Added `@Transactional` decorator to relevant methods to ensure proper transaction management.
- Enhanced error handling in the `Transactional` decorator to raise specific exceptions.
- Updated tests to clear session state before and after tests to maintain isolation and prevent side effects.
- Implement outermost transaction detection using SessionContextHolder.has_session()
- Session lifecycle (commit/rollback/close) only managed by outermost @transactional
- Nested @transactional methods reuse existing session without interference
- Prevent premature session closure in nested transaction scenarios
- Maintain transaction integrity across multiple decorated method calls
- Removed commented-out explanations for clarity.
- Streamlined the docstring to focus on the transaction behavior of the decorator.
@NFUChen NFUChen self-assigned this Jun 19, 2025
@NFUChen NFUChen added the enhancement New feature or request label Jun 19, 2025
@NFUChen NFUChen changed the title Feat/persistence context Implement Context-based Transactional Session Management Jun 19, 2025
- Added TransactionalDepth enum to define transaction levels.
- Enhanced SessionContextHolder with methods to manage session depth.
- Updated @transactional decorator to utilize session depth for commit/rollback operations.
- Introduced tests for session depth tracking and behavior in nested transactions.
- Changed the condition for clearing the session from checking if the depth is 0 to using TransactionalDepth.ON_EXIT.value for better clarity and alignment with transaction management.
…ved transaction management

- Added @transactional decorator to multiple CRUD methods in CrudRepository to ensure proper transaction handling.
- Updated commit logic in PySpringSession to warn against committing managed transactions.
- Refactored clone method in PySpringModel to return the correct type.
- Improved logging during session commit in PySpringModel for better clarity.
- Introduced a method in SessionContextHolder to check if a transaction is managed.
- Introduced TypeVar for PySpringModel to improve type hinting in the clone method.
- Updated the clone method signature to return the correct type, enhancing clarity and type safety.
- Introduced __version__ attribute to specify the current version of the package.
- Updated __all__ to include Query for better module accessibility.
@NFUChen NFUChen merged commit c761f5d into main Jul 17, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants