Ten Architecture Anti-Patterns & Operational Pitfalls
An exceptional framework baseline defines not only what developers should do, but explicitly codifies what is strictly forbidden. During fast-paced feature development, subtle architectural smells can easily compromise system resilience, trigger thread pool starvation, or cause disastrous multi-tenant data leaks.
Authored from a senior full-stack architect’s perspective, this runbook details 10 fatal architectural anti-patterns and their corresponding defensive principles.
1. Sync-over-Async Thread Pool Starvation
- Fatal Anti-Pattern: Calling
.Resultor.Wait()on asynchronous tasks inside Mediator pipeline behaviors or command handlers:// Strictly forbidden: easily exhausts ASP.NET Core thread pools under loadvar user = _userRepository.GetByIdAsync(userId).Result; -
- Defensive Invariant: Implement end-to-end
async/awaitpassingCancellationTokenthrough every layer to the underlying storage driver.
- Defensive Invariant: Implement end-to-end
2. Cross-Module Layer Penetration
- Fatal Anti-Pattern: Referencing
Identity.InfrastructureorIdentity.Domaindirectly inLitigation.Application.csproj. -
- Defensive Invariant: Modules reference only
*.Contractsof other modules at compile time. Cross-module communication uses CAP transactional outbox events or read-only store interfaces declared in Contracts. This invariant is enforced by automatedArchitectureTests.
- Defensive Invariant: Modules reference only
3. Multi-Tenant Cache & Raw SQL Leakage
- Fatal Anti-Pattern: Omitting
TenantIdin Dapper queries or caching tenant-scoped data in a global Redis key:// Strictly forbidden: Tenant A reads Tenant B cached summariesawait _cache.SetAsync("active_case_summary", summary); -
- Defensive Invariant: Prefix all cache keys with tenant namespaces (e.g.,
$"tenant:{tenantId}:cases:{caseId}"). Every raw SQL query must explicitly includeTenantId = @TenantIdin itsWHEREclause.
- Defensive Invariant: Prefix all cache keys with tenant namespaces (e.g.,
4. Local Clock Table Partition Drift
- Fatal Anti-Pattern: Calling
DateTime.Nowinside table partitioning routines. When deployed inside container hosts with differing host clocks, daily partitions drift across time zones. -
- Defensive Invariant: Standardize entirely on UTC (
DateTimeOffset.UtcNow). Time zone conversions are presentation-layer responsibilities only.
- Defensive Invariant: Standardize entirely on UTC (
5. DTO Proliferation in Simple CRUD
- Productivity Anti-Pattern: Hand-crafting 15 separate single-use DTOs and AutoMapper mappings for a three-column lookup table.
-
- Defensive Invariant: Follow the framework’s isomorphic aggregate principle. Internal vertical slices reuse strongly typed aggregate roots directly; external DTOs are defined only when traversing physical network boundaries.
6. Blocking Outbound Calls in Long Transactions
- Distributed Anti-Pattern: Invoking external payment gateways or third-party judicial APIs directly inside a relational database transaction block, holding row locks for seconds.
-
- Defensive Invariant: Commit database transactions rapidly. Write outbound intentions to the transactional Outbox (CAP), allowing asynchronous background workers to handle network retries.
7. Throwing Raw Exceptions for Business Logic
- Error Handling Anti-Pattern: Throwing
throw new Exception("Insufficient balance")to convey routine business rejection. -
- Defensive Invariant: Business rejections are not crashes; return strongly typed
Result.Failure(ModuleErrors.Code). Reserve exceptions strictly for physical failures (e.g., network partitions), handled globally via RFC 9457 ProblemDetails.
- Defensive Invariant: Business rejections are not crashes; return strongly typed
8. Magic String Pollution
- Maintainability Anti-Pattern: Littering application logic with arbitrary strings such as
"PENDING","01", or"ERR_FAIL". -
- Defensive Invariant: Declare all status codes as enums or smart enums; declare error codes in static readonly
*Errorscatalogs for global searchability.
- Defensive Invariant: Declare all status codes as enums or smart enums; declare error codes in static readonly
9. Live Migration Table-Locking Index Drops
- Operational Anti-Pattern: Running
DROP INDEXand recreating indexes on multi-million-row production tables during live deployments. -
- Defensive Invariant: BitzOrcas catalog probing prohibits dropping indexes on existing tables. Only missing tables and columns (
ALTER TABLE ADD NULLwithLOCK_TIMEOUT 5000) are modified during live rollout.
- Defensive Invariant: BitzOrcas catalog probing prohibits dropping indexes on existing tables. Only missing tables and columns (
10. Unsupervised Autonomous Agent Mutations
- AI Governance Anti-Pattern: Permitting autonomous models to execute irreversible payouts, deletions, or contract revocations without human confirmation.
-
- Defensive Invariant: High-risk mutations enforce Human-in-the-Loop (HITL) via the Prepare & Confirm pattern, requiring human operator MFA Step-Up confirmation before execution.
11. Related Architecture Decisions & Deep Dives
- Rule Lifecycle: ADR 0001: Architecture Rule Lifecycle
- Quality Gates: ADR 0002: Production Readiness & Quality Gates
- Security Checklist: Production Security Checklist & Hardening