UnitOfWork
Transaction boundary for persistence operations. Used as a context manager to group multiple repository operations into a single atomic transaction.
See Unit of Work guide for practical usage.
Transaction boundary for persistence operations.
Groups one or more repository operations into an atomic unit. Use as a context manager to ensure that all changes within the block are committed together or rolled back on error::
with UnitOfWork():
repo = domain.repository_for(Order)
order = repo.get(order_id)
order.confirm()
repo.add(order)
Command handlers and the @use_case decorator wrap their execution in a
UnitOfWork automatically, so explicit usage is typically only needed in
application services or scripts.
Nesting joins the outermost transaction. A UnitOfWork started while another is already active on the same context does not open its own transaction. There are no savepoints, so it joins the outermost one: every write, read, and event routes to the outermost UnitOfWork, and only that one commits or rolls back. A nested rollback rolls back the whole transaction. This is why an application service called from within a command handler's UnitOfWork composes into a single transaction rather than committing independently. Independent inner transactions (a savepoint, or a sub-transaction that commits on its own) are not supported, by design: the aggregate is the consistency boundary and one UnitOfWork maps to one use case. For cross-aggregate coordination use domain events; for a durable side-effect that must survive a rollback use the outbox.
The UnitOfWork maintains an identity map to track loaded aggregates and collects domain events raised during the transaction. On commit, events are persisted to the outbox and dispatched to brokers/event store.
Source code in src/protean/core/unit_of_work.py
87 88 89 90 91 92 93 94 95 96 97 98 99 100 | |
start
start() -> None
Begin the transaction and push this UnitOfWork onto the context stack.
Opens no session, deliberately. The session, and on SQLAlchemy the
real BEGIN, appears at the first repository access through
:meth:_initialize_session. ADR-0031 turns that into a contract: a
handler method reaching no repository runs no transaction and holds no
pooled connection, which is what lets a handler talk to an external
system by putting the call in its own method. Opening a session here
would silently pin a connection for the length of every such call, so
this method must stay free of any eager connection or session.
A UnitOfWork started while another is already active on this context does
not open its own transaction. There are no savepoints, so it joins the
active (outermost) UnitOfWork: it does not push onto the stack, so
repository operations keep resolving current_uow to the outermost UoW
and route every write, session, and event there. Its own commit and
rollback then defer to the outermost UoW (see commit/rollback).
Source code in src/protean/core/unit_of_work.py
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | |
commit
commit() -> None
Commit all changes, persist outbox messages, and dispatch events.
| RAISES | DESCRIPTION |
|---|---|
InvalidOperationError
|
If the UnitOfWork is not in progress. |
ExpectedVersionError
|
On optimistic concurrency conflict. |
TransactionError
|
If the underlying database commit fails. |
Source code in src/protean/core/unit_of_work.py
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 | |
rollback
rollback() -> None
Roll back all changes and close sessions.
| RAISES | DESCRIPTION |
|---|---|
InvalidOperationError
|
If the UnitOfWork is not in progress. |
Source code in src/protean/core/unit_of_work.py
550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 | |
get_session
get_session(provider_name: str) -> SessionProtocol
Get session for provider, initializing one if it doesn't exist
Source code in src/protean/core/unit_of_work.py
610 611 612 613 614 615 | |