Asset Data Integrity
Summary
Source-backed map of the integrity controls protecting Asset data — domain invariants on the aggregate, application-layer checks, database constraints and indexes, and expectations that are not enforced anywhere — presented as a classification matrix.
Audience
Backend engineers, database reviewers, architects, QA, support, operations, security reviewers, and product owners.
Overview
Asset integrity is layered. The Asset aggregate enforces lifecycle and assignment invariants in domain code; application commands enforce tenant presence, code-duplication, and workflow guards; the database enforces primary keys, two foreign keys, three unique indexes, and NOT NULL / length constraints. Several business expectations — serial uniqueness, one active assignment, and cross-service reference validity — are not enforced at the database level.
Confirmed persistence behavior
Integrity matrix
| Rule | Domain-enforced | Application-enforced | Database-enforced | UI-only | Unenforced expectation |
|---|---|---|---|---|---|
Valid lifecycle transition (bad transition throws DomainValidationException) | Yes | — | — | — | — |
| Assignable status / assignee required to assign | Yes | — | — | — | — |
Only Issued may return | Yes | — | — | — | — |
Only Requested may approve / reject; rejection reason required | Yes | Reject-reason also checked | — | — | — |
| Retired is terminal (already-retired throws; retired cannot be edited) | Yes | — | — | — | — |
Deletion eligibility (IsDeletable = Available / Requested / Rejected) | Yes | Gate before delete | — | — | — |
| Document-reference ownership (children owned by aggregate) | Yes | — | FK AssetId → Assets (cascade, required) | — | — |
Tenant context required for writes (RequireTenantId) | Factory rejects tenantId ≤ 0 | Yes (RequireTenantId) | — | — | — |
Per-tenant asset-code uniqueness (CodeExistsAsync, 409 Conflict) | — | Yes | No — AssetCode index is non-unique | — | — |
| Workflow callback idempotency (Status ≠ Requested → acknowledge) | — | Yes | — | — | — |
| Workflow-managed guard (direct action while managed → 409) | — | Yes | — | — | — |
| Clearance blocking states (Issued / Damaged / Lost / Repair) | — | Query-level, read-only | — | — | — |
| Primary-key uniqueness (7 keys, all Guid) | — | — | Yes | — | — |
CompatId uniqueness | — | — | Yes (UX_Assets_CompatId) | — | — |
Outbox / processed-event EventId uniqueness | — | — | Yes (unique indexes) | — | — |
One current custodian (single Assignee on the aggregate) | Aggregate holds a single assignee | — | No DB constraint | — | Concurrent assigns could each read Available |
| Serial-number uniqueness | — | — | No | — | Not enforced anywhere |
| Employee / labor / workflow / document reference validity | — | — | No FK (identifier-only) | — | Cross-service validity not enforced |
| Concurrency protection on transitions | Status re-check only | — | No concurrency token | — | See Concurrency |
Notes on each layer
- Domain invariants live on the
Assetaggregate: tenant required (> 0), valid state transitions, assignee required to assign, only-Issued-returns, only-Requested-approves/rejects, rejection reason required, retired terminal, and theIsDeletablegate. Violations throwDomainValidationException. - Application checks (
AssetCommands, compatibility/workflow integration):RequireTenantId(), theCodeExistsAsyncduplicate check surfaced as 409, the workflow-managed guard (409), and workflow-callback idempotency. - Database constraints: seven primary keys, two cascade foreign keys (history → asset, document reference → asset), three unique indexes (
CompatId, outboxEventId, processed-eventEventId), and NOT NULL / max-length on required columns. - Not database-enforced: per-tenant code uniqueness (application-only, and a unique index would be global-across-tenants), serial uniqueness (none), one-active-assignment (none), and all cross-service references, which are identifier-only with no foreign keys.
Classification
Implemented layered integrity with domain and application coverage; several business-level uniqueness and cross-service expectations are Unenforced at the database level by design.
Requires confirmation
Whether serial uniqueness, database-level per-tenant code uniqueness, a one-active-assignment constraint, and concurrency protection should be hardened requires product and database confirmation.
Diagram
Related Articles
See Also
Keywords
- Asset persistence
- Data Integrity
- Draft database documentation
Source References
microservices/src/asset-service/Domain/Asset/Asset.csmicroservices/src/asset-service/Application/Commands/AssetCommands.csmicroservices/src/asset-service/Infrastructure/AssetDbContext.csmicroservices/src/asset-service/Infrastructure/Migrations/AssetDbContextModelSnapshot.cs
Revision Information
- Status: Draft
- Last reviewed: 2026-07-17
- Review cycle: Quarterly