Chapter 25 · Production Architecture, DDD/CQRS Integration, Reliability, and Capstone
Capstone: Design, Implement, Test, Tune, Secure, Deploy, and Defend a Production EF Core Data Layer
Require an evidence-backed ServiceHub production data-layer defense spanning model, SQL, tracking, concurrency, transactions, migrations, tenancy, security, tests, observability, performance, deployment and intentional non-EF paths.
Learning outcomes
Assemble a complete ServiceHub production data layer from the course decisions rather than starting a new toy model.
Defend the provider, model, loading/tracking, concurrency, transaction and migration choices with observable evidence.
Prove tenant isolation/security with adversarial tests and least-privilege deployment/runtime separation.
Benchmark one hot read path with realistic data, query tags and database plan evidence.
Operate the design with health checks, metrics, outbox/backlog visibility, backup/restore and incident runbooks.
Explain where EF Core is intentionally not used and why that boundary improves the system.
1. Capstone brief: defend ServiceHub as if another team must operate it
The final deliverable is not “a CRUD API that runs.” You are the owner of a multi-tenant ServiceHub data layer that another team must deploy, test, monitor and repair. Every major decision needs an artifact: model configuration, migration SQL, generated command, tracker evidence, test result, execution plan, metric, runbook or explicit provider constraint.
A design claim without observable evidence is incomplete. “EF handles it” is not evidence; show what EF generated, what the provider/database enforced, and what your tests/operations prove.
2. Required architecture: one portable core, explicit provider boundaries
Use the established SQLite mandatory path for a free local capstone and optionally qualify SQL Server/PostgreSQL/MySQL with the provider matrix from Chapters 19–21. Keep provider-specific features behind intentional adapters/configuration, not accidental LINQ scattered across the application.
| Concern | Required capstone choice |
|---|---|
| Aggregate/write model | Encapsulated WorkOrder + ServiceAddress + Revision + tenant/lifecycle invariants |
| Read model | Bounded DTO projection; optional SQL/Dapper hot path only with evidence |
| Consistency | SaveChanges/explicit transaction + optimistic concurrency resolution |
| Integration | Transactional outbox + idempotent relay |
| Schema | Reviewed migrations + deployment artifact + expand/contract plan |
| Security | named tenant filters + authorization + DB constraints/least privilege; optional RLS |
| Operations | health, metrics/logs/traces, slow-query/plan correlation, backup/restore and runbooks |
3. The capstone command path
public sealed class ReviseWorkOrderCommandHandler( ServiceHubContext db, IWorkOrderRepository orders, TimeProvider clock){ public async Task HandleAsync(int id, string summary, CancellationToken ct) { var order = await orders.LoadForRevisionAsync(id, ct) ?? throw new KeyNotFoundException(); order.ReviseSummary(summary, clock); // invariant + Revision + domain event await db.SaveChangesAsync(ct); // aggregate + outbox in one transaction }}
UPDATE "work_orders"SET "summary" = @summary, "revision" = @newRevisionWHERE "work_order_id" = @id AND "revision" = @originalRevision;INSERT INTO "outbox_messages" (...)VALUES (...);-- both participate in the same SaveChanges transaction
The review must explain tenant filter/write ownership, the original-versus-new revision predicate, outbox atomicity and how a concurrency conflict is surfaced/resolved.
4. The capstone read path must prove shape and plan
var query = db.WorkOrders .TagWith("ServiceHub.Capstone.Queue.v1") .AsNoTracking() .Where(w => w.Priority >= 3) .OrderByDescending(w => w.Priority) .ThenBy(w => w.OpenedUtc) .Take(100) .Select(w => new WorkOrderQueueRow( w.Id, w.WorkOrderNumber, w.Summary, w.Priority, w.ServiceAddress.City));Console.WriteLine(query.ToQueryString());var rows = await query.ToListAsync(ct);
Capture the generated SQL, actual row count, data distribution
and SQLite EXPLAIN QUERY PLAN (or the production
provider’s actual plan tool). If you replace this path with raw
SQL/Dapper, repeat the same correctness/security/performance
evidence.
5. Test matrix: happy paths are the least interesting rows
| Suite | Minimum proof |
|---|---|
| Domain unit | invalid summary/transition rejected without database |
| Relational lightweight | FK/unique/tenant constraints + migration-aware SQLite setup |
| Concurrency | two contexts read same Revision; loser gets DbUpdateConcurrencyException |
| Transaction/outbox | forced failure commits neither aggregate nor outbox |
| Tenant red-team | cross-tenant reads/writes/filter bypass/admin path constrained |
| Provider fidelity | production-like engine translation/migration tests where applicable |
| Performance | bounded regression baseline + plan/query-count evidence |
| Deployment | representative prior schema → current migration + restore/rollback rehearsal |
6. Security acceptance criteria
The capstone must keep SQL values parameterized, dynamic query
choices allow-listed, credentials out of source/logs, runtime DB
identity separate from migration authority where the provider
supports roles, sensitive EF logging disabled by default, and
tenant isolation enforced in more than one layer. A successful
IgnoreQueryFilters admin operation must have
explicit authorization and audit evidence.
7. Deliberate failure review: identify architecture smells before launch
[ ] Generic repository that mirrors every DbSet method[ ] IQueryable exposed to untrusted/API consumers without shape policy[ ] External broker publish inside SavingChanges[ ] Every read materializes tracked aggregates[ ] Dapper/raw SQL bypasses tenant predicate or transaction ownership[ ] ExecuteUpdate used where aggregate events/concurrency tokens are required[ ] App replicas run schema migration on startup by default[ ] Pool/timeout values copied from a blog without target measurements[ ] EnableSensitiveDataLogging enabled in ordinary production[ ] SQLite tests claimed as proof of SQL Server/PostgreSQL semantics[ ] Down migration treated as the backup/rollback plan
Each checked item needs either a repair or a documented, tested reason it is safe in this topology.
8. Production evidence packet
Submit a small evidence bundle alongside the code:
- Model: WorkOrder metadata, complex ServiceAddress columns, tenant filters, keys/FKs/indexes and Revision token.
- SQL: one command update and one hot query with parameters/tags.
- Tracker: before/after DebugView for one aggregate change.
- Migrations: reviewed SQL/bundle plan, migration-history state and expand/contract note.
- Tests: concurrency, transaction/outbox, tenant red-team and provider-fidelity results.
- Performance: data volume/distribution, warmup/cache disclosure, query plan and bounded latency baseline.
- Operations: dashboard signals, backup/restore evidence and two incident runbooks.
9. Mandatory final lab: design, implement, test, tune, secure and deploy
- Start from the accumulated ServiceHub project—not a new sample.
- Implement one tenant-aware WorkOrder command with aggregate invariant, Revision and domain event.
- Persist its outbox row atomically and run an idempotent local relay.
- Implement the bounded queue read; capture SQL and plan; optionally compare Dapper 2.1.79.
- Run the Chapter 23 migration/concurrency/transaction/query-count/provider tests and Chapter 22 tenant adversarial tests.
- Generate a deployment migration artifact and execute it against a disposable environment with a separate deployment configuration.
- Run readiness/observability checks, inject one timeout/lock/query-regression incident, and follow the runbook.
- Perform a SQLite-aware backup and restore verification for the mandatory local path.
- Write a one-page architecture defense: where EF Core is used, where it is not, and which evidence supports every exception.
10. Course completion: the transferable skill is prediction and evidence
You have completed the EF Core path when you can predict the model/query/tracker/write behavior, inspect what EF generates, identify what the provider/database actually guarantees, and choose architecture/operations from evidence. EF Core is neither a black box nor a religion: it is one part of a relational data system whose correctness depends on domain boundaries, SQL semantics, transactions, deployment, security, testing and observability working together.
Check your understanding
- What makes the capstone more than a CRUD exercise?
- Why must the architecture defense state where EF is not used?
- What proves optimistic concurrency?
- What proves tenant isolation?
- What proves a performance change is real?
- When is the course complete?
Review the answers
1. It requires evidence for model/query/write semantics plus concurrency, transactions, migrations, security, tests, performance, observability and deployment/runbooks.
2. Specialized paths may be better served by raw SQL/Dapper/native APIs; making the boundary explicit prevents accidental provider/security/transaction drift.
3. Two independent contexts plus a generated update predicate containing the original token and a deterministic loser conflict.
4. Adversarial tests plus application authorization/filter policy and database constraints/least privilege; optional RLS where supported.
5. Controlled workload/data distribution, generated SQL, plan, repeated measurement and disclosed cache/network/provider conditions.
6. When the learner can predict, observe, test and defend EF/database behavior across development and production operations.
Authoritative references
- DbContext lifetime/configuration — unit-of-work lifecycle and production context rules
- EF Core testing strategy — real-database/provider fidelity expectations
- Applying migrations — production schema deployment strategies and migration locking
- EF Core performance guidance — pooling/query/runtime performance boundaries
- CQRS pattern — intentional read/write model separation
- EF Core SQL queries — safe handwritten SQL boundaries and parameterization