Skip to main content

Asset Concurrency

Summary

Source-backed statement of the Asset Service's concurrency posture: there is no concurrency token, optimistic-concurrency mapping, pessimistic lock, or compare-and-swap anywhere in the source. The only safeguards are lifecycle status re-checks, workflow-callback idempotency, and unique indexes — which reduce but do not guarantee concurrency safety.

Audience

Backend engineers, database reviewers, architects, QA, support, operations, security reviewers, and product owners.

Overview

Concurrent Asset writes are not protected by any database or EF-level concurrency mechanism. The service relies on re-reading the aggregate and re-validating its lifecycle state on each transition, plus idempotent workflow callbacks and unique indexes on identity columns. These narrow, but do not eliminate, the window for lost updates.

Confirmed persistence behavior

Verified absences

  • No RowVersion / Timestamp / concurrency token and no xmin mapping anywhere in the source (verified: no RowVersion, ConcurrencyToken, or IsConcurrencyToken usage).
  • No optimistic concurrency — EF Core is not configured to detect concurrent modification on any entity.
  • No pessimistic lock — the source issues no row locks (no FOR UPDATE-style locking).
  • No compare-and-swap update pattern.

Safeguards that do exist

  • Lifecycle status re-check on every transition: each command loads the aggregate and the domain method throws if the asset is not in a valid source state (for example, only Issued may return, only Requested may approve/reject). A transition attempted against the wrong state fails rather than silently applying.
  • Workflow-callback idempotency: when a workflow callback arrives and the asset's Status is no longer Requested, the callback is acknowledged without re-applying the effect, so duplicate callbacks do not double-process.
  • Unique indexes: CompatId (UX_Assets_CompatId) and the outbox / processed-event EventId unique indexes prevent duplicate identity or duplicate-event rows at the database.

Why this is not concurrency safety

These mechanisms reduce, but do not guarantee, safety under concurrency:

  • Because there is no concurrency token, two requests that both load the aggregate while it is Available can both pass the status re-check and proceed to assign — a classic lost-update / double-assignment window. The status re-check only compares against the state each request read, not against a version stamp validated at write time.
  • The unique indexes protect identity and event-id uniqueness only; they do not protect lifecycle transitions or the single-assignee invariant.
  • Callback idempotency protects against duplicate workflow callbacks, not against concurrent direct commands.

No claim of concurrency safety is made. Interleaved concurrent transitions on the same asset can still race.

Classification

Transitional — lifecycle re-checks, callback idempotency, and unique indexes only; no first-class concurrency control.

Requires confirmation

Whether a concurrency token (for example a row-version column) or a database-level single-assignment constraint should be added to close the double-assignment window requires product and database confirmation.

See Also

Keywords

  • Asset persistence
  • Concurrency
  • Draft database documentation

Source References

  • microservices/src/asset-service/Domain/Asset/Asset.cs
  • microservices/src/asset-service/Application/Commands/AssetCommands.cs
  • microservices/src/asset-service/Infrastructure/AssetDbContext.cs
  • microservices/src/asset-service/Infrastructure/Migrations/AssetDbContextModelSnapshot.cs

Revision Information

  • Status: Draft
  • Last reviewed: 2026-07-17
  • Review cycle: Quarterly