# NFR Design Patterns — Unit: Offerings ## Pattern 1: Transactional Multi-Row Operations (Data Integrity) **Decision** (Q1 = A): every `OfferingsService` operation that touches more than one row in a single logical action runs inside one explicit DB transaction (`BeginTransactionAsync`/`CommitAsync`, rolled back on any exception): - **Featured-exclusivity swap** (US-10): un-featuring the previously-featured offering and saving the newly-featured one. - **Full reorder** (US-08): reassigning `DisplayOrder` across the entire list from the drag-and-drop UI. - **Adjacent swap** (US-09): swapping `DisplayOrder` between two neighboring offerings. **Rationale**: a crash or connection failure mid-operation must never leave the dataset in an inconsistent state (two offerings both un-featured, duplicate `DisplayOrder` values). This closes the open item `services.md` deferred to Functional Design but that was never actually decided there. **Pattern**: standard EF Core `DbContext.Database.BeginTransactionAsync()` wrapping the read-modify-write sequence within each of the three `OfferingsService` methods (`CreateAsync`/`UpdateAsync` when `Featured` transitions to `true`, `ReorderAsync`, `MoveUpAsync`/`MoveDownAsync`). Single-row operations (plain create/update without a featured transition, soft-delete) do not need an explicit transaction — a single `SaveChangesAsync()` call is already atomic. ## Pattern 2: Rate Limiting Applied at the Action Level **Decision**: `[EnableRateLimiting("offerings-public")]` is placed on the public `GET` action method only, following the exact precedent in `AuthController` (`[EnableRateLimiting("login")]`/`[EnableRateLimiting("refresh")]` on individual actions, not the whole controller). `OfferingsController`'s admin mutation actions carry no rate-limiting attribute — consistent with NFR-OFF-01 (Q1 = A) scoping the policy to the public endpoint only. **No new pattern beyond this** — the `offerings-public` policy itself (config-driven `FixedWindowLimiter`) is already fully specified in `tech-stack-decisions.md`. ## Pattern 3: Audit Logging — Structured Fields **Decision**: `OfferingsService` emits one `LogInformation` structured log entry per create/update/delete, with these fields: `OfferingId` (Guid), `Action` (`"Created"`/`"Updated"`/`"Deleted"`), `LastModifiedByUserId` (Guid, the same value persisted on the entity). No before/after value diffing — consistent with NFR-OFF-03's accepted scope (minimal audit trail, not a full audit-log table). ## No Other New Patterns Scalability and Performance categories are N/A for this unit (see `offerings-nfr-design-plan.md`) — no new pattern required beyond what's already covered above and in `nfr-requirements.md`.