Unit Of Work Implementer
by @quochungto
Implement Unit of Work (UoW) — the object that tracks new, dirty, clean, and removed entities during a business operation and commits all database changes to...
Scenario A: Java e-commerce — custom Data Mapper, hand-rolled UoW
Trigger: "We have a Java e-commerce service with Order, LineItem, and Product. We're using hand-rolled Data Mappers (no ORM). After a business operation touches 12 objects, we're making 12 separate UPDATE calls. How do we introduce a Unit of Work?"
Process:
1. Confirm Data Mapper in place. FK graph: LineItem references Order and Product.
2. Registration strategy: object registration — setters on Order and LineItem call UoW.getCurrent().registerDirty(this).
3. UoW API: registerNew / registerDirty / registerClean / registerRemoved / commit().
4. Identity Map keyed by (Class, Long id); populated on OrderMapper.find(id).
5. Commit sequence: INSERT Order → INSERT LineItem (Product pre-exists) → UPDATE dirty Orders → UPDATE dirty LineItems → DELETE removed LineItems → DELETE removed Orders → COMMIT.
6. Lifecycle: per-HTTP-request via servlet filter — UnitOfWork.newCurrent() on request start; UnitOfWork.getCurrent().commit() + setCurrent(null) on request end (in finally block).
Output: Hand-rolled UnitOfWork class with three lists (new/dirty/removed), ThreadLocal storage for current UoW, DomainObject base class with markDirty() / markNew() / markRemoved(), and per-request lifecycle managed by a servlet filter. See references/entity-state-transitions.md for full Java sketch.
Scenario B: Python + SQLAlchemy — scoping the built-in Session
Trigger: "We use SQLAlchemy with a FastAPI app. We're seeing stale data and occasional DetachedInstanceError. How should we scope the Session?"
Process:
1. Data Mapper confirmed: SQLAlchemy ORM's mapped classes + Session is the UoW.
2. Registration strategy: UoW-controlled — SQLAlchemy tracks changes automatically; session.add(entity) registers new objects.
3. Problem diagnosis: Session is likely being shared across requests (application-scoped singleton) rather than per-request.
4. Fix: use a dependency-injected Session per FastAPI request via Depends(get_db), where get_db yields a session and closes it after the request.
5. Commit sequence: handled by session.commit() — SQLAlchemy resolves INSERT ordering via mapper relationships; session.flush() pushes SQL without committing for mid-operation ID resolution.
6. Lazy Load: SQLAlchemy lazy proxies use the session for population; closed or detached sessions trigger DetachedInstanceError. Fix: load eagerly for data needed after session close, or keep session open for the request lifetime.
Output: get_db generator dependency, per-request session scope, session.add for new entities, session.delete for removed, session.commit() at end of each request handler (or in a middleware). Anti-pattern warning: never use a module-level Session instance.
Scenario C: .NET + EF Core — DbContext per-request scoping
Trigger: "We have an ASP.NET Core app with EF Core. We're trying to understand when to call SaveChanges and how to avoid detached entity errors."
Process:
1. Data Mapper confirmed: EF Core DbContext is the UoW; entities tracked by the change tracker.
2. Registration strategy: UoW-controlled — EF Core detects changes on tracked entities automatically.
3. DbContext is registered as Scoped in ASP.NET Core DI → one instance per HTTP request. This is correct.
4. Commit: await dbContext.SaveChanges() at the end of the service method (or in a controller action). Avoid calling it multiple times per request unless intentional.
5. Optimistic Offline Lock: add a [Timestamp] or [ConcurrencyToken] property; EF Core adds WHERE version=? automatically and throws DbUpdateConcurrencyException on collision.
6. Anti-pattern: passing a DbContext from a scoped service into a singleton service → context outlives the request, accumulates stale data.
Output: Confirm Scoped lifetime, single SaveChanges() call per business operation, [ConcurrencyToken] on entities needing optimistic locking, and warning against singleton-scoped DbContext.
clawhub install bookforge-unit-of-work-implementer