Unit of Work & Deferred Events
Deep dive into atomic transaction boundaries and why domain events must be decoupled from database commits.
In distributed applications, one of the most common sources of data corruption and phantom side-effects is dispatching notifications or domain events before a database transaction is guaranteed to commit. ZCore's UnitOfWork (UoW) eliminates the "Dual-Write" problem by buffering events and coordinating atomic multi-repository boundaries.
1. The Atomic Transaction Boundary
UnitOfWork is an asynchronous context manager. Upon entering the context (__aenter__), it injects a state flag directly into the active SQLAlchemy session metadata:
# Internal lifecycle flag set by UnitOfWork
self.session.info["uow_managed"] = TrueThis flag signals BaseService instances to delegate commit authority. When any service method (create, update, delete) invokes its internal _safe_commit(), it inspects session.info["uow_managed"]. Detecting an active UoW, the service skips its individual commit, allowing multiple repositories to participate in a single atomic transaction.
If any exception is raised inside the async with block, UnitOfWork catches the error, calls session.rollback(), and purges the pending event queue.
2. Nested & Re-entrant Coordination (Depth Tracking)
In modular monolithic architectures, domain services frequently invoke each other (e.g., OrderService calling InventoryService and BillingService). If each service declares its own UnitOfWork boundary, a naive implementation would prematurely commit when the inner service finishes.
ZCore solves this with Depth-Aware Re-entrancy:
- Depth Counter (
uow_depth):UnitOfWork.__aenter__incrementssession.info["uow_depth"]. - Nested Flushes: When an inner scope (
depth > 1) exits,uow.commit()executesawait session.flush()instead of a physical commit. This assigns database-generated IDs, validates foreign key constraints, and synchronizes session state without ending the physical database transaction. - Shared Event Buffer (
uow_events): Domain events registered at any nesting depth are buffered in a shared list atsession.info["uow_events"]. - Root Commit & Atomic Dispatch: Only when the outermost context manager (
depth == 0) concludes does ZCore execute the physical database commit and dispatch all accumulated domain events concurrently viaEventDispatcher. - Chain Rollback: If an exception is raised at any depth, the active depth resets to
0,uow_managedis cleared, all pending events are discarded, and the entire multi-service transaction is rolled back immediately.
3. Deferred Event Dispatching (Solving the Dual-Write Problem)
Standard event dispatchers trigger listener callbacks immediately when dispatch() is called. In transactional systems, this creates severe consistency bugs:
The Premature Event Trap:
db.flush()(Order inserted into session)dispatcher.dispatch("order.completed")(Sends order confirmation email)db.commit()(Fails due to a serialization or unique constraint error!)
The Bug: The database transaction rolled back (no order exists), but the customer already received a confirmation email!
The ZCore Solution
Inside a UnitOfWork, calling uow.register_event("order.completed", payload) does not execute listeners immediately. Instead, it buffers the event tuple in the session's pending events store:
async with UnitOfWork(self.repository.db, self.dispatcher) as uow:
order = await self.order_repo.create(order_data)
await self.inventory_repo.decrement_stock(product_id, quantity)
# Buffered safely: Not dispatched yet!
uow.register_event("order.completed", {"order_id": str(order.id)})
# Dispatches ONLY after the physical database commit succeeds on root exit!Only after await self.session.commit() completes successfully does the UoW pop each event from _pending_events and invoke self.dispatcher.dispatch(...). If the commit fails, all buffered events are permanently discarded.
This provides absolute transactional consistency: If a domain event fires, the database state is guaranteed to be committed.
4. Integration with BaseRepository
Because all repository instances resolve the active, request-scoped AsyncSession managed by ScopedDependencyMiddleware, they automatically participate in the UnitOfWork lifecycle without any custom configuration. You can orchestrate updates across multiple independent domain repositories within a single atomic UoW block.
Handler Failure Isolation:
During post-commit event dispatching, if an individual async listener raises an unhandled exception (e.g., an external webhook timeout), UnitOfWork catches and logs the failure with full traceback diagnostics, ensuring remaining event handlers continue to execute without interrupting the request lifecycle.
Dynamic Search Engine & Security
How ZCore compiles nested JSON filters into safe SQL, protects against DoS attacks, and enforces column-level security.
Zchema & Context Shielding
Understand how ZCore intercepts Pydantic V2 to provide dynamic, role-based field pruning across validation, serialization, and OpenAPI schema generation.