Skip to content

[Refactor] Outbox Architecture Overhaul: PostgreSQL Support, Mixin Models, and UUID v7 #32

@Dioneya

Description

@Dioneya

Problem

The current implementation of the cqrs.outbox.sqlalchemy module has several architectural limitations:

  1. MySQL Vendor-lock: Reliance on the func.UUID_TO_BIN function makes the library incompatible with PostgreSQL or SQLite.
  2. Migration Issues (Alembic): The OutboxModel is bound to the library's internal Base. This complicates migration generation in user applications since Alembic cannot detect this table in the target_metadata.
  3. Index Performance: Using random UUID v4 as a Primary Key (or part of a composite index) causes B-Tree index fragmentation, degrading INSERT performance over time.
  4. Hard Dependency: SQLAlchemy is currently a mandatory dependency, even if the user intends to use a different backend or strictly in-memory implementation.

Solution

This PR proposes a comprehensive refactoring of the Outbox module:

  1. Transition to Mixin Architecture
  • Replaced the hardcoded OutboxModel with OutboxModelMixin.
  • Users now define the model themselves by inheriting from their own DeclarativeBase and the provided mixin.
  • This resolves Alembic integration issues and allows customization of tablename.
  1. Cross-Database Compatibility (PostgreSQL support)
  • Removed dependency on the MySQL-specific UUID_TO_BIN function.
  • Implemented a custom BinaryUUID type (TypeDecorator).
    • PostgreSQL: Uses the native UUID type.
    • MySQL/SQLite: Uses optimized BINARY(16) storage.
  1. Performance Optimization (UUID v7)
  • Added uuid6 dependency.
  • Switched default event_id generation from uuid4 to uuid7.
  • UUID v7 is time-ordered (monotonically increasing), ensuring sequential writes to the B-Tree index and eliminating fragmentation.
  1. Optional SQLAlchemy Dependency
  • Moved sqlalchemy to optional-dependencies (pip install python-cqrs[sqlalchemy]).
  • Wrapped internal library imports in try-except blocks to prevent crashes when the driver is not installed.

New Usage Example

from sqlalchemy.orm import DeclarativeBase
from cqrs.outbox import OutboxModelMixin

# Define the model in the user application
class Base(DeclarativeBase):
    pass

class MyOutbox(Base, OutboxModelMixin):
    __tablename__ = "outbox_events"

# Initialize the repository
repo = SqlAlchemyOutboxedEventRepository(
    session=session,
    outbox_model=MyOutbox  # Pass the model class
)

Checklist

  • Implement OutboxModelMixin.
  • Implement BinaryUUID for PostgreSQL and MySQL compatibility.
  • Switch ID generation to uuid7.
  • Make sqlalchemy an optional dependency.
  • Update tests (using testcontainers or mocks).
  • Update documentation (README).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions