Skip to content

HTTP Request Context Management

Jararaca provides Unit of Work (UoW) context management for HTTP requests, ensuring that database transactions, message publishing, and WebSocket communications are handled atomically. This document covers how the UoW context system works and how to use it.

Overview

The UoW context ensures that: - Database transactions are properly scoped to requests - Messages are only published if the database transaction succeeds (transactional outbox pattern) - WebSocket messages are dispatched after successful database commits - All operations within a request share the same transaction context

Current Implementation: HttpUowContextProviderDependency

Jararaca currently implements UoW context management using FastAPI's dependency injection system through the HttpUowContextProviderDependency class.

from fastapi import Depends
from jararaca.presentation.server import HttpUowContextProviderDependency, create_http_server
from jararaca.presentation.http_microservice import HttpMicroservice

# The create_http_server function automatically sets up the UoW context
http_app = HttpMicroservice(app=app)
fastapi_app = create_http_server(http_app)

# Under the hood, it does this:
uow_provider = UnitOfWorkContextProvider(app, container)
uow_dependency = HttpUowContextProviderDependency(uow_provider)

fastapi_app.router.dependencies.append(
    Depends(uow_dependency)
)

The HttpUowContextProviderDependency automatically: 1. Extracts endpoint metadata from the request 2. Sets up the UoW context with all configured interceptors 3. Manages the transaction boundary 4. Handles errors and converts them to appropriate HTTP responses

Request Lifecycle

When a request comes in, the following happens:

  1. Request Received → FastAPI invokes the dependency
  2. Context SetupHttpUowContextProviderDependency sets up the UoW context
  3. Interceptors Activated → All configured interceptors are invoked in order:
  4. Configuration Interceptor (if any)
  5. Message Bus Publisher Interceptor (stages messages)
  6. Database Session Interceptor (begins transaction)
  7. WebSocket Interceptor (stages WebSocket messages)
  8. Handler Execution → Your controller method executes
  9. Commit Phase → Interceptors commit in reverse order:
  10. Database commits first
  11. Messages are published (only if DB commit succeeded)
  12. WebSocket messages are dispatched
  13. Response Returned → FastAPI sends the response

Error Handling

The dependency automatically handles errors:

try:
    # Your endpoint code executes
    yield
except HTTPException:
    # FastAPI's HTTP exceptions pass through
    raise
except Exception as e:
    # Other exceptions become 500 Internal Server Error
    logger.exception("Unhandled exception in request handling.")
    raise HTTPException(
        status_code=500,
        detail={
            "message": "Internal server error occurred.",
            "x-traceparentid": response.headers.get("traceparent")
        }
    ) from e

WebSocket Support

The same system works for WebSocket connections:

@RestController("/ws")
class WebSocketController:
    @WebSocketEndpoint("/chat")
    async def chat_endpoint(self, websocket: WebSocket) -> None:
        # The UoW context is automatically available
        # You can use use_session(), use_publisher(), etc.
        pass

The HttpUowContextProviderDependency detects whether it's handling an HTTP request or WebSocket connection and sets up the appropriate context.

Usage in Controllers

Once the UoW context is set up, you can use context hooks anywhere in your code:

from jararaca import RestController, Post, use_session, use_publisher

@RestController("/api/users")
class UserController:
    @Post("/")
    async def create_user(self, user_data: dict) -> dict:
        # Access the database session
        session = use_session()

        # Create user
        user = User(**user_data)
        session.add(user)

        # Stage a message for publishing
        publisher = use_publisher()
        await publisher.publish(UserCreatedEvent(user_id=user.id))

        # Transaction commits and message publishes automatically
        return user.to_dict()

Integration with Interceptors

The UoW context works seamlessly with all interceptors. See the Interceptors documentation for details on:

  • How interceptor order affects transaction boundaries
  • Implementing custom interceptors
  • Transactional outbox pattern
  • Message staging and flushing

Complete Example

Here's a complete example showing how to set up a microservice with UoW context management:

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from jararaca import (
    AIOSQAConfig,
    AIOSqlAlchemySessionInterceptor,
    HttpMicroservice,
    MessageBusPublisherInterceptor,
    Microservice,
    WebSocketInterceptor,
)
from jararaca.presentation.server import create_http_server


def fastapi_factory(lifespan):
    """Custom FastAPI factory with CORS."""
    app = FastAPI(lifespan=lifespan)

    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )

    return app

# Create microservice with interceptors
app = Microservice(
    name="my-service",
    interceptors=[
        # Message bus for event publishing
        MessageBusPublisherInterceptor(...),

        # Database sessions
        AIOSqlAlchemySessionInterceptor(
            AIOSQAConfig(
                connection_name="default",
                url="postgresql+asyncpg://user:pass@localhost/mydb",
                inject_default=True,
            )
        ),

        # WebSocket management
        WebSocketInterceptor(...),
    ],
    controllers=[UserController, OrderController],
)

# Create HTTP server - UoW context is automatically configured
http_app = HttpMicroservice(app=app, factory=fastapi_factory)
fastapi_app = create_http_server(http_app)

Best Practices

  1. Use Context Hooks: Always access resources via use_session(), use_publisher(), etc.
  2. Let Interceptors Manage Transactions: Don't manually commit/rollback unless necessary
  3. Order Interceptors Correctly: Configuration → Message Bus → Database → WebSocket
  4. Handle Errors Gracefully: Let the dependency convert exceptions to HTTP responses
  5. Test with Mocked Context: Use fixtures to provide test contexts

Advanced: Manual Context Management

In rare cases, you might need to manually manage the UoW context (e.g., in background tasks):

from jararaca.core.uow import UnitOfWorkContextProvider
from jararaca.microservice import AppTransactionContext
from jararaca.di import Container

async def background_task():
    """Execute a background task with UoW context."""
    container = Container(app)
    uow_provider = UnitOfWorkContextProvider(app, container)

    # Create transaction context manually
    async with uow_provider(
        AppTransactionContext(
            controller_member_reflect=member_reflect,
            transaction_data=transaction_data,
        )
    ):
        # Your background task code
        session = use_session()
        # ... perform operations

Note: This is rarely needed as most operations should happen within request handlers where the context is automatically provided.

Troubleshooting

"No session set in the context"

This error means you're trying to use use_session() outside of a UoW context. Ensure: - You're calling it from within a controller method - The HttpUowContextProviderDependency is properly configured - You're not trying to use it in module-level code

Transactions Not Committing

If changes aren't being persisted: - Check that no exceptions are being raised - Verify interceptor order is correct - Ensure you're not manually rolling back the transaction

Messages Not Publishing

If messages aren't being published: - Verify the database transaction succeeded - Check that the Message Bus interceptor is configured before the Database interceptor - Review logs for publishing errors

See Also