🎁 Get the FREE AI Skills Starter Guide β€” Subscribe β†’
BytesAgainBytesAgain
πŸ¦€ ClawHub

Pessimistic Offline Lock Implementer

by @quochungto

Use when offline-concurrency-strategy-selector (or your team) has chosen Pessimistic Offline Lock and you need to implement it correctly end-to-end. Handles:...

Versionv1.0.0
⚑ When to Use
TriggerAction
The core trade-off: Optimistic Offline Lock detects conflicts at commit time (user loses work already done). Pessimistic Offline Lock prevents conflicts at load time (user sees "locked by Alice" immediately, before doing any work). Use Pessimistic when late failure is genuinely unacceptable β€” insurance policy underwriting, complex order editing, legal document authoring.
**Do not use** if collisions are rare and rework cost is low (use optimistic instead). **Do not use** if the entire workflow fits in a single DB transaction (use isolation levels).
πŸ’‘ Examples

Example 1: Java/Spring β€” Insurance Underwriting System

Scenario: Underwriters edit complex insurance policies. Sessions last 45–90 minutes. Policies have Coverages, Endorsements, and Named Insureds. Two underwriters occasionally assigned the same policy. Work loss cost: very high.

Trigger: "Underwriters are furious about 409 errors after 90 minutes of work. Need to lock policies at edit start."

Process:

  • Phase 1: Correctness depends on reading fresh data (actuarial tables) β†’ Exclusive read lock.
  • Phase 2: Single DB, existing Postgres β†’ DB lock table. Schema: app_lock(lockable_id BIGINT PK, owner_id VARCHAR, lock_type VARCHAR, acquired_at TIMESTAMP, expires_at TIMESTAMP).
  • Phase 3: Protocol β€” EditPolicyCommand acquires lock before policyMapper.find(id). SavePolicyCommand releases after commit. LockRemover registered on HTTP session as HttpSessionBindingListener. Expiry: 120 minutes.
  • Phase 4: Policy + Coverages + Endorsements + Named Insureds = aggregate β†’ Shared version token (one version row per Policy; all members reference it). Lock on version.id.
  • Phase 5: Multiple command objects in the application β†’ LockingMapper decorator wraps all policy-family mappers. find() acquires; update() verifies lock held.
  • UX: "Policy 12345 is being edited by Bob Smith (since 10:47 AM). Available after 12:47 PM. [Request force-release]"
  • Output: DB lock table, 120-minute expiry, LockingMapper, shared version token for Policy aggregate, admin /admin/locks/policy/{id}/release endpoint, LockRemover session listener.


    Example 2: Node.js CMS β€” Article Editing with Redis

    Scenario: CMS where editors write and edit published articles. Sessions typically 20–30 minutes. Articles consist of Article + Sections + Tags + Metadata. Node.js, no JVM session management.

    Trigger: "Add edit locking to prevent two editors opening the same article simultaneously."

    Process:

  • Phase 1: Stale reads acceptable (viewing out-of-date draft is OK) β†’ Exclusive write lock.
  • Phase 2: Redis already in stack β†’ Redis lock with TTL. Key: lock:article:{id}, value: { ownerId, ownerName, acquiredAt }, TTL: 2400s (40 min).
  • Phase 3: GET /articles/:id/edit acquires Redis lock before loading. PUT /articles/:id releases after save. Express middleware registers session-end cleanup via req.session.on('destroy', releaseAllLocksFor(sessionId)). Heartbeat: client pings /session/ping every 30s; absence for 5 minutes triggers server-side expiry.
  • Phase 4: Article + Sections + Tags + Metadata β†’ use root lock on Article ID (all children navigate to Article as root). One Redis key per article covers the aggregate.
  • Phase 5: All article repository methods go through ArticleRepository base class. findForEdit(id) acquires lock; update(article) asserts lock is held.
  • UX: "This article is currently being edited by Jane (since 2:10 PM). Try again in ~25 minutes or ask Jane to release the lock."
  • Output: Redis lock with TTL + heartbeat, root lock on Article (aggregate), ArticleRepository.findForEdit() acquires implicitly, session-destroy listener.


    Example 3: Python/Django β€” Order-Picking System

    Scenario: Warehouse pickers claim orders from a queue. Once a picker opens an order, it must be locked so two pickers don't pick the same items. Sessions are short (5–15 min, order completion). DB: PostgreSQL.

    Trigger: "Two pickers sometimes pick the same order. Add a 'picked by X' locking mechanism with visible status."

    Process:

  • Phase 1: Only pickers who intend to edit (pick) need a lock; browsing the queue is read-only β†’ Exclusive write lock.
  • Phase 2: Single Postgres DB β†’ DB lock table. Include expires_at for 30-minute absolute timeout.
  • Phase 3: POST /orders/{id}/claim acquires lock before loading. POST /orders/{id}/complete releases. Django signal request_started + session middleware for release-on-session-end. Picker's name stored as owner_id (human-readable for UI).
  • Phase 4: Order + LineItems + Inventory Reservations = aggregate. Use root lock on Order ID. All members navigable from Order. Lock the Order ID only.
  • Phase 5: OrderRepository.claim_for_picking(order_id, picker_id) is the single entry point; implicit lock built in. No LockingMapper needed (single access path).
  • UX: Order card in queue shows "PICKED BY: John D. (since 9:05 AM)" badge. Admin dashboard shows all active locks. Auto-released after 30 minutes if order not completed.
  • Output: app_lock table with expires_at, expiry sweep task, Order root lock, claim_for_picking() as single locked access point, UI "PICKED BY" badge, admin lock dashboard.

    View on ClawHub
    TERMINAL
    clawhub install bookforge-pessimistic-offline-lock-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 β†’