🎁 Get the FREE AI Skills Starter GuideSubscribe →
BytesAgainBytesAgain
🦀 ClawHub

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...

Versionv1.0.0
When to Use
TriggerAction
business operation — newly created, loaded and modified, or deleted — and then flushes all
changes to the database together in the correct order inside a single system transaction.
Use this skill when:
- You have a Data Mapper layer and need change tracking discipline across a business operation
- Your code makes too many database round trips (one UPDATE per field change, not one per commit)
- You are scoping an ORM session (Hibernate `Session`, EF `DbContext`, SQLAlchemy `Session`) and want to understand the underlying contract
- You are implementing a custom Data Mapper layer without a framework and need to track dirty objects
- You are wiring optimistic locking and need a single commit point to run version-conditioned updates
**Prerequisite:** Data Mapper must be the chosen data-source pattern. If it has not been selected, invoke `data-source-pattern-selector` first, or ask the user to confirm their persistence approach before proceeding. UoW adds a coordination layer that Active Record codebases do not need.
---
💡 Examples

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.


View on ClawHub
TERMINAL
clawhub install bookforge-unit-of-work-implementer

🧪 Use this skill with your agent

Most visitors already have an agent. Pick your environment, install or copy the workflow, then run the smoke-test prompt above.

🔍 Can't find the right skill?

Search 60,000+ AI agent skills — free, no login needed.

Search Skills →