Skip to content

[Proposal] Extract Transactional Outbox Pattern into a Separate Library #34

@Dioneya

Description

@Dioneya

Proposal: Extract Transactional Outbox Pattern into a Separate Library

I propose extracting the Transactional Outbox implementation (currently in src/cqrs/outbox) into a standalone library (e.g., python-transactional-outbox). Below are the key architectural and practical reasons for this decision.

1. Single Responsibility Principle (SRP) Violation

The core responsibility of python-cqrs is to provide abstractions for the Mediator pattern, Command/Query handling, and Event Dispatching.
The Transactional Outbox pattern is a persistence and reliability mechanism. While it complements CQRS, it is a distinct architectural concern. Coupling them tightly forces the core library to manage database schemas, ORM integrations (SQLAlchemy), and background workers, which dilutes its primary focus.

2. Hard Coupling to Infrastructure

Currently, the Outbox implementation is tightly coupled with:

  • SQLAlchemy: Users who want to use python-cqrs with MongoDB, Tortoise ORM, or raw SQL drivers are forced to pull in SQLAlchemy dependencies or deal with dead code.
  • Specific Database Dialects: The current implementation contains MySQL-specific optimizations (e.g., UUID_TO_BIN), which hinders portability to PostgreSQL or SQLite.

By extracting it, python-cqrs becomes infrastructure-agnostic, while the new library can focus on providing robust, database-specific implementations (adapters) for the Outbox pattern.

3. Independent Lifecycle and Scalability

The Outbox pattern often requires complex features that are irrelevant to the core CQRS mediator:

  • Debezium Integration (CDC): Advanced Outbox implementations often rely on Change Data Capture rather than polling.
  • Message Relay/Worker: The logic for polling, locking, retrying, and publishing messages to a broker (Kafka/RabbitMQ) is complex and deserves its own lifecycle, configuration, and scaling strategy.
  • Observability: Metrics for outbox lag, publishing rates, and failure handling are specific to the Outbox domain.

Developing these features within python-cqrs bloats the library. A separate library allows for independent versioning and faster iteration on these specific reliability features.

4. Reusability

The Transactional Outbox pattern is useful even for applications that do not use CQRS. A developer might want to ensure atomic event publishing in a standard CRUD application.
Currently, they cannot use your Outbox implementation without installing the entire python-cqrs framework. Extracting it makes the solution reusable across the entire Python ecosystem.

5. Dependency Management

The current setup forces a heavy dependency tree (sqlalchemy, drivers, pydantic, etc.) on all users. Separating the libraries allows for a cleaner dependency graph:

  • python-cqrs: Lightweight, focuses on dispatching logic.
  • python-outbox: Focuses on persistence guarantees.
  • python-outbox-sqlalchemy: Optional adapter.

Conclusion

Extracting the Outbox pattern aligns python-cqrs with the Hexagonal Architecture (Ports and Adapters) approach. It reduces the core library's complexity, increases flexibility for users to choose their persistence stack, and allows the Outbox implementation to evolve into a mature, standalone product-grade reliability tool independent of the CQRS framework.

Originally posted by @Dioneya in #32

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