From c326f5d87048694a313771df60847fc2b7d382fc Mon Sep 17 00:00:00 2001 From: mghabin <81494213+MohammadGhabin@users.noreply.github.com> Date: Thu, 30 Apr 2026 18:38:01 +0300 Subject: [PATCH 1/2] docs: final-verify cleanup (synthesis pointers, source discipline, anchor fixes) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - patterns/patterns.md: drop the cross-cutting doctrine block (vertical slice vs Clean Architecture, bounded contexts, domain events) — patterns.md is a thin index, not a doctrine owner. Anchor slugs corrected throughout. - docs/decision-trees.md Tree 13 (Caching): OutputCache row now points at ch02 §9 as the OWNER; ch03 §14 stays for the data-cache matrix only. Drop the 'Default per ch03' framing. - coverage-map.md ch06: ServiceDefaults caveat — it ships only /health + /alive in Development; production must explicitly MapHealthChecks for /health/live, /health/ready, /health/startup. Cite ch06 §10 + aspire.dev. Chapter section anchor slugs corrected (no more #chapter-NN--name). - checklist.md §9 AuthN/AuthZ: route to local owners first (docs/02-aspnetcore.md#10-authnauthz, decision-trees Tree 12); the external Entra sample is demoted to a non-normative 'See also'. - docs/01-foundations.md Sources: rebalance to <60% learn.microsoft / ≥40% non-learn primary by adding ECMA-334, ECMA-335, dotnet/csharplang LDM notes (C# 13/14 proposals, NRT spec, Records proposal), dotnet/roslyn source generator cookbook + interceptors, dotnet/runtime GC + Tiered/PGO + DI + LoggerMessage + exceptions design docs, RFC 9110, devblogs posts (Stephen Toub async internals, Mads Torgersen NRT/Records, OptionsValidator announcement), Sigstore. Final ratio: 57.9% learn / 42.1% non-learn. - Remove Medium and YouTube links from chapter Sources blocks (docs/02-aspnetcore.md, docs/03-data.md, docs/04-testing.md, docs/05-performance.md). Replace maoni0.medium.com with devblogs.microsoft.com/dotnet/author/maoni and dotnet/runtime GC docs. - docs/05-performance.md: move 'Community / people to follow' out of the canonical Sources block into a 'Further reading (non-normative)' section at the end of the chapter. - Fix all broken internal anchors. The script-defined slug algorithm collapses runs of whitespace to a single hyphen, so '#chapter-01--foundations' → '#chapter-01-foundations', '#5-di--lifetimes' → '#5-di-lifetimes', etc. '`docs/05-performance.md` line ~410: stale '#chapter-01--foundations' on 01-foundations.md (the slug lives in coverage-map.md) → repointed at ch01 §10 + the coverage-map ownership entry. Anchor verification script now reports 'all anchors resolve'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- checklist.md | 16 ++--- coverage-map.md | 132 ++++++++++++++++++++--------------------- docs/01-foundations.md | 36 ++++++++--- docs/02-aspnetcore.md | 1 - docs/03-data.md | 2 +- docs/04-testing.md | 1 - docs/05-performance.md | 25 ++++---- docs/decision-trees.md | 27 +++++---- patterns/patterns.md | 65 +++++++------------- 9 files changed, 153 insertions(+), 152 deletions(-) diff --git a/checklist.md b/checklist.md index 9912d1b..e9b5df1 100644 --- a/checklist.md +++ b/checklist.md @@ -90,7 +90,7 @@ See: [`docs/01-foundations.md`](./docs/01-foundations.md), decision tree [11 — - Read `IConfiguration` directly inside business logic. - "Optional" config that silently no-ops in prod. -See: [`docs/01-foundations.md`](./docs/01-foundations.md), [`docs/06-cloud-native.md#4-configuration--configmap--csi-key-vault-not-appsettingsproductionjson`](./docs/06-cloud-native.md#4-configuration--configmap--csi-key-vault-not-appsettingsproductionjson). +See: [`docs/01-foundations.md`](./docs/01-foundations.md), [`docs/06-cloud-native.md#4-configuration--configmap--csi-key-vault-not-appsettingsproductionjson`](./docs/06-cloud-native.md#4-configuration-configmap-csi-key-vault-not-appsettingsproductionjson). ## 6. Logging @@ -107,7 +107,7 @@ See: [`docs/01-foundations.md`](./docs/01-foundations.md), [`docs/06-cloud-nativ - Access tokens, refresh tokens, client secrets, cookies — even truncated — in logs. - `catch { _log.LogError(ex, "boom"); throw; }` — log **or** throw, not both at every layer. -See: [`docs/01-foundations.md`](./docs/01-foundations.md) (primitives) and [`docs/06-cloud-native.md#5-observability--opentelemetry-one-sdk-three-signals`](./docs/06-cloud-native.md#5-observability--opentelemetry-one-sdk-three-signals) (OTLP exporter wiring). +See: [`docs/01-foundations.md`](./docs/01-foundations.md) (primitives) and [`docs/06-cloud-native.md#5-observability--opentelemetry-one-sdk-three-signals`](./docs/06-cloud-native.md#5-observability-opentelemetry-one-sdk-three-signals) (OTLP exporter wiring). ## 7. HTTP (outbound) & resilience @@ -156,7 +156,7 @@ See: [`docs/02-aspnetcore.md`](./docs/02-aspnetcore.md) §§3, 4, 5, 8. - Keep `MapInboundClaims = false` so `scp` / `roles` / `azp` stay verbatim, and validate `iss`, `aud`, signing key, `exp`, `nbf`; pin tenant where applicable ([learn.microsoft.com/entra/identity-platform/access-token-claims-reference](https://learn.microsoft.com/entra/identity-platform/access-token-claims-reference), [RFC 9068](https://datatracker.ietf.org/doc/html/rfc9068)). - **Two separate named policies** per capability: a delegated policy that requires the `scp` scope (and rejects tokens carrying `roles` without `scp`); an app-only policy that requires `roles` **and** an `azp`/`appid` allow-list **and** the absence of `scp`. - For endpoints that legitimately accept both flows, list both policies on the endpoint (`RequireAuthorization("XDelegated", "XApp")`) so each policy still enforces its own invariants. -- Use `RequiredScope` / policy-based authorization, not ad-hoc claim sniffing in handlers; reference [`mghabin/entra-auth-patterns-dotnet`](https://github.com/mghabin/entra-auth-patterns-dotnet) for the runnable sample. +- Use `RequiredScope` / policy-based authorization, not ad-hoc claim sniffing in handlers. **Don't:** @@ -165,7 +165,7 @@ See: [`docs/02-aspnetcore.md`](./docs/02-aspnetcore.md) §§3, 4, 5, 8. - Accept tokens with `ver=1.0` when you expect `2.0` (or vice versa) without explicit handling. - Disable `ValidateIssuer`, `ValidateAudience`, or `ValidateLifetime`. Ever. -See: [`docs/02-aspnetcore.md#10-authnauthz`](./docs/02-aspnetcore.md#10-authnauthz), decision tree [12 — auth policy shape](./docs/decision-trees.md#12-auth-policy-shape-delegated-scp-vs-app-only-roles--azp). +See: [`docs/02-aspnetcore.md#10-authnauthz`](./docs/02-aspnetcore.md#10-authnauthz) (owner) and decision tree [12 — auth policy shape](./docs/decision-trees.md#12-auth-policy-shape-delegated-scp-vs-app-only-roles-azp). See also (non-normative, runnable sample): [`mghabin/entra-auth-patterns-dotnet`](https://github.com/mghabin/entra-auth-patterns-dotnet). ## 10. Caching @@ -203,14 +203,14 @@ See: [`docs/02-aspnetcore.md#9-output-caching`](./docs/02-aspnetcore.md#9-output - Lazy loading enabled in web request paths. - Distributed (two-phase / MSDTC) transactions across DB + bus / DB + HTTP / two databases — there is no DTC on Linux .NET, and most managed services don't enlist. -See: [`docs/03-data.md#6-transactions--unit-of-work`](./docs/03-data.md#6-transactions--unit-of-work) (outbox is the single owner here), and decision tree [17 — Cosmos partition key](./docs/decision-trees.md#17-cosmos-db-partition-key) for the modelling rule. +See: [`docs/03-data.md#6-transactions--unit-of-work`](./docs/03-data.md#6-transactions-unit-of-work) (outbox is the single owner here), and decision tree [17 — Cosmos partition key](./docs/decision-trees.md#17-cosmos-db-partition-key) for the modelling rule. ## 12. Background work **Do:** - `BackgroundService` / `IHostedService`; honor the `stoppingToken` in every loop ([learn.microsoft.com/dotnet/core/extensions/workers](https://learn.microsoft.com/dotnet/core/extensions/workers)). -- Graceful shutdown: drain in-flight work within `HostOptions.ShutdownTimeout`; the cluster-side drain contract is owned by [`docs/06-cloud-native.md#11-graceful-shutdown--drain-dont-drop`](./docs/06-cloud-native.md#11-graceful-shutdown--drain-dont-drop). +- Graceful shutdown: drain in-flight work within `HostOptions.ShutdownTimeout`; the cluster-side drain contract is owned by [`docs/06-cloud-native.md#11-graceful-shutdown--drain-dont-drop`](./docs/06-cloud-native.md#11-graceful-shutdown-drain-dont-drop). - Idempotency keys on every externally-visible side effect; safe to replay (same `(key, tenant)` UNIQUE store the outbox/inbox uses — see §11). - Bound concurrency with `Channel`, `Parallel.ForEachAsync`, or a semaphore — not unbounded `Task.Run`. @@ -278,7 +278,7 @@ See: [`docs/05-performance.md`](./docs/05-performance.md), decision trees [9 — - Client secrets in pipelines when FIC works. - Default in-memory Data Protection keys behind a load balancer. -See: [`docs/06-cloud-native.md#10-health-checks--three-endpoints-for-k8s-not-what-servicedefaults-gives-you`](./docs/06-cloud-native.md#10-health-checks--three-endpoints-for-k8s-not-what-servicedefaults-gives-you) (probe contract owner; ch02 §18 only owns the `MapHealthChecks` plumbing), [`docs/06-cloud-native.md#1-net-aspire--what-it-is-what-it-isnt`](./docs/06-cloud-native.md#1-net-aspire--what-it-is-what-it-isnt), decision tree [16 — Aspire scope](./docs/decision-trees.md#16-aspire-scope-apphost-resource-vs-in-service-client-integration). +See: [`docs/06-cloud-native.md#10-health-checks--three-endpoints-for-k8s-not-what-servicedefaults-gives-you`](./docs/06-cloud-native.md#10-health-checks-three-endpoints-for-k8s-not-what-servicedefaults-gives-you) (probe contract owner; ch02 §18 only owns the `MapHealthChecks` plumbing), [`docs/06-cloud-native.md#1-net-aspire--what-it-is-what-it-isnt`](./docs/06-cloud-native.md#1-net-aspire-what-it-is-what-it-isnt), decision tree [16 — Aspire scope](./docs/decision-trees.md#16-aspire-scope-apphost-resource-vs-in-service-client-integration). ## 16. Security @@ -296,7 +296,7 @@ See: [`docs/06-cloud-native.md#10-health-checks--three-endpoints-for-k8s-not-wha - Write tokens or keys to local disk; use `DataProtection`, Key Vault, or memory only. - Disable certificate validation ("just for staging"). -See: [`docs/02-aspnetcore.md#14-security`](./docs/02-aspnetcore.md#14-security), [`docs/06-cloud-native.md#8-secrets--identity--workload-identity-only`](./docs/06-cloud-native.md#8-secrets--identity--workload-identity-only). +See: [`docs/02-aspnetcore.md#14-security`](./docs/02-aspnetcore.md#14-security), [`docs/06-cloud-native.md#8-secrets--identity--workload-identity-only`](./docs/06-cloud-native.md#8-secrets-identity-workload-identity-only). ## 17. Dependencies diff --git a/coverage-map.md b/coverage-map.md index ee0ea73..616ccf3 100644 --- a/coverage-map.md +++ b/coverage-map.md @@ -42,18 +42,18 @@ Source: [`docs/01-foundations.md`](./docs/01-foundations.md). ### References (does not re-decide) -- DI usage inside endpoints and filters → [Chapter 02 — aspnetcore](#chapter-02--aspnetcore). -- EF Core `DbContext` lifetime and pooling → [Chapter 03 — data](#chapter-03--data). -- Hosted-service testing patterns → [Chapter 04 — testing](#chapter-04--testing). -- NativeAOT, GC tuning specifics, container env-vars → [Chapter 05 — performance](#chapter-05--performance). -- OTLP exporters and `ServiceDefaults` wiring → [Chapter 06 — cloud-native](#chapter-06--cloud-native). -- Blazor / MAUI client lifetimes and render modes → [Chapter 07 — client](#chapter-07--client). +- DI usage inside endpoints and filters → [Chapter 02 — aspnetcore](#chapter-02-aspnetcore). +- EF Core `DbContext` lifetime and pooling → [Chapter 03 — data](#chapter-03-data). +- Hosted-service testing patterns → [Chapter 04 — testing](#chapter-04-testing). +- NativeAOT, GC tuning specifics, container env-vars → [Chapter 05 — performance](#chapter-05-performance). +- OTLP exporters and `ServiceDefaults` wiring → [Chapter 06 — cloud-native](#chapter-06-cloud-native). +- Blazor / MAUI client lifetimes and render modes → [Chapter 07 — client](#chapter-07-client). ### Cross-links -- Logging primitives owned here ↔ telemetry export owned in [06](#chapter-06--cloud-native) and meter design in [05](#chapter-05--performance). -- DI lifetimes owned here ↔ endpoint DI surface in [02](#chapter-02--aspnetcore) ↔ `DbContext` scoping in [03](#chapter-03--data). -- Options pattern owned here ↔ secrets/config in cloud in [06](#chapter-06--cloud-native). +- Logging primitives owned here ↔ telemetry export owned in [06](#chapter-06-cloud-native) and meter design in [05](#chapter-05-performance). +- DI lifetimes owned here ↔ endpoint DI surface in [02](#chapter-02-aspnetcore) ↔ `DbContext` scoping in [03](#chapter-03-data). +- Options pattern owned here ↔ secrets/config in cloud in [06](#chapter-06-cloud-native). --- @@ -73,27 +73,27 @@ Source: [`docs/02-aspnetcore.md`](./docs/02-aspnetcore.md). - **HTTP resilience for outbound calls** (§7): `IHttpClientFactory` + `AddStandardResilienceHandler` as the single owner of retry / timeout / circuit-breaker / hedging defaults. Chapter 06 references this section, does not re-decide. - **`HttpClient` factory and typed clients** (§11): `AddHttpClient`, named vs typed, `SocketsHttpHandler` defaults, `PooledConnectionLifetime`. - **OutputCache** (§9): HTTP-response caching middleware (`AddOutputCache`, policies, tag invalidation). Chapter 03's caching matrix links here for the OutputCache row. -- **Background work in the request host** (§12): `IHostedService` / `BackgroundService` patterns, `Channel` pipelines, graceful drain of in-flight work — composition lifetime owned here; the cluster-side drain contract is owned by [06 §11](#chapter-06--cloud-native). +- **Background work in the request host** (§12): `IHostedService` / `BackgroundService` patterns, `Channel` pipelines, graceful drain of in-flight work — composition lifetime owned here; the cluster-side drain contract is owned by [06 §11](#chapter-06-cloud-native). - **Security middleware** (§14): HTTPS redirection, HSTS, security headers, antiforgery, request-size limits, forwarded headers (paired with §15). - **gRPC surface** (§16): `MapGrpcService`, gRPC-Web, code-first vs proto-first, interceptors, deadlines. - **SignalR surface** (§17): hub authorization, backplane choice, scaling rules, sticky sessions. -- **Health-check endpoint mapping** (§18): the in-process `MapHealthChecks` helpers and how the request host exposes them — but the **probe contract** (endpoint names, semantics, what a check may do) is owned by [06 §10](#chapter-06--cloud-native). +- **Health-check endpoint mapping** (§18): the in-process `MapHealthChecks` helpers and how the request host exposes them — but the **probe contract** (endpoint names, semantics, what a check may do) is owned by [06 §10](#chapter-06-cloud-native). - Rate limiting middleware (§8), request size limits, and Kestrel surface defaults relevant to APIs. ### References (does not re-decide) -- Logging primitives and source-generated loggers → [Chapter 01 — foundations](#chapter-01--foundations). +- Logging primitives and source-generated loggers → [Chapter 01 — foundations](#chapter-01-foundations). - Validation libraries and model-binding edge cases → [`docs/01-foundations.md`](./docs/01-foundations.md) and `validation.md` referenced from chapter 02. -- EF Core `DbContext` registration and async query patterns → [Chapter 03 — data](#chapter-03--data). -- `WebApplicationFactory` integration tests → [Chapter 04 — testing](#chapter-04--testing). -- OTel meters and application metrics → [Chapter 05 — performance](#chapter-05--performance). -- OTLP exporters, `ServiceDefaults`, K8s probes → [Chapter 06 — cloud-native](#chapter-06--cloud-native). +- EF Core `DbContext` registration and async query patterns → [Chapter 03 — data](#chapter-03-data). +- `WebApplicationFactory` integration tests → [Chapter 04 — testing](#chapter-04-testing). +- OTel meters and application metrics → [Chapter 05 — performance](#chapter-05-performance). +- OTLP exporters, `ServiceDefaults`, K8s probes → [Chapter 06 — cloud-native](#chapter-06-cloud-native). ### Cross-links -- ProblemDetails owned here ↔ EF Core exception translation in [03](#chapter-03--data). -- JWT bearer + Entra owned here ↔ Blazor auth-state serialization in [07](#chapter-07--client). -- OTel HTTP wiring owned here ↔ OTel application metrics in [05](#chapter-05--performance) ↔ OTLP exporters in [06](#chapter-06--cloud-native). +- ProblemDetails owned here ↔ EF Core exception translation in [03](#chapter-03-data). +- JWT bearer + Entra owned here ↔ Blazor auth-state serialization in [07](#chapter-07-client). +- OTel HTTP wiring owned here ↔ OTel application metrics in [05](#chapter-05-performance) ↔ OTLP exporters in [06](#chapter-06-cloud-native). --- @@ -114,17 +114,17 @@ Source: [`docs/03-data.md`](./docs/03-data.md). ### References (does not re-decide) -- DI lifetimes underlying `DbContext` registration → [Chapter 01 — foundations](#chapter-01--foundations). -- Returning `ProblemDetails` for data-layer failures → [Chapter 02 — aspnetcore](#chapter-02--aspnetcore). -- Respawn-based test data reset and Testcontainers for SQL/Cosmos → [Chapter 04 — testing](#chapter-04--testing). -- Span/Memory/Pipelines for high-throughput data paths → [Chapter 05 — performance](#chapter-05--performance). -- Outbox dispatcher hosted in the cluster, message broker integrations → [Chapter 06 — cloud-native](#chapter-06--cloud-native). +- DI lifetimes underlying `DbContext` registration → [Chapter 01 — foundations](#chapter-01-foundations). +- Returning `ProblemDetails` for data-layer failures → [Chapter 02 — aspnetcore](#chapter-02-aspnetcore). +- Respawn-based test data reset and Testcontainers for SQL/Cosmos → [Chapter 04 — testing](#chapter-04-testing). +- Span/Memory/Pipelines for high-throughput data paths → [Chapter 05 — performance](#chapter-05-performance). +- Outbox dispatcher hosted in the cluster, message broker integrations → [Chapter 06 — cloud-native](#chapter-06-cloud-native). ### Cross-links -- Outbox owned here ↔ broker/dispatcher topology in [06](#chapter-06--cloud-native). -- Caching matrix owned here ↔ Data Protection key ring (separate concern) in [06](#chapter-06--cloud-native). -- Migration bundles owned here ↔ K8s init-container / job invocation in [06](#chapter-06--cloud-native). +- Outbox owned here ↔ broker/dispatcher topology in [06](#chapter-06-cloud-native). +- Caching matrix owned here ↔ Data Protection key ring (separate concern) in [06](#chapter-06-cloud-native). +- Migration bundles owned here ↔ K8s init-container / job invocation in [06](#chapter-06-cloud-native). --- @@ -145,18 +145,18 @@ Source: [`docs/04-testing.md`](./docs/04-testing.md). ### References (does not re-decide) -- Code under test conventions (NRT, async, DI lifetimes) → [Chapter 01 — foundations](#chapter-01--foundations). -- Endpoint construction and ProblemDetails contracts being asserted → [Chapter 02 — aspnetcore](#chapter-02--aspnetcore). -- EF Core fixtures, migrations, and Cosmos provider behavior → [Chapter 03 — data](#chapter-03--data). -- BenchmarkDotNet and perf assertions → [Chapter 05 — performance](#chapter-05--performance). -- Aspire AppHost wiring under test → [Chapter 06 — cloud-native](#chapter-06--cloud-native). -- Blazor `bUnit` and MAUI device-test specifics → [Chapter 07 — client](#chapter-07--client). +- Code under test conventions (NRT, async, DI lifetimes) → [Chapter 01 — foundations](#chapter-01-foundations). +- Endpoint construction and ProblemDetails contracts being asserted → [Chapter 02 — aspnetcore](#chapter-02-aspnetcore). +- EF Core fixtures, migrations, and Cosmos provider behavior → [Chapter 03 — data](#chapter-03-data). +- BenchmarkDotNet and perf assertions → [Chapter 05 — performance](#chapter-05-performance). +- Aspire AppHost wiring under test → [Chapter 06 — cloud-native](#chapter-06-cloud-native). +- Blazor `bUnit` and MAUI device-test specifics → [Chapter 07 — client](#chapter-07-client). ### Cross-links -- `DistributedApplicationTestingBuilder` owned here ↔ Aspire AppHost in [06](#chapter-06--cloud-native). -- `WebApplicationFactory` owned here ↔ JWT bearer + ProblemDetails in [02](#chapter-02--aspnetcore). -- Respawn owned here ↔ EF migrations in [03](#chapter-03--data). +- `DistributedApplicationTestingBuilder` owned here ↔ Aspire AppHost in [06](#chapter-06-cloud-native). +- `WebApplicationFactory` owned here ↔ JWT bearer + ProblemDetails in [02](#chapter-02-aspnetcore). +- Respawn owned here ↔ EF migrations in [03](#chapter-03-data). --- @@ -176,17 +176,17 @@ Source: [`docs/05-performance.md`](./docs/05-performance.md). ### References (does not re-decide) -- `async`/`await`, `ValueTask`, source generators as the language baseline → [Chapter 01 — foundations](#chapter-01--foundations). -- HTTP-pipeline OTel instrumentation that produces transport metrics → [Chapter 02 — aspnetcore](#chapter-02--aspnetcore). -- EF Core query-shape choices that dominate allocations → [Chapter 03 — data](#chapter-03--data). -- Microbenchmark fixtures vs integration perf tests → [Chapter 04 — testing](#chapter-04--testing). -- OTLP exporters, collector, and K8s CPU/QoS that the metrics are read from → [Chapter 06 — cloud-native](#chapter-06--cloud-native). +- `async`/`await`, `ValueTask`, source generators as the language baseline → [Chapter 01 — foundations](#chapter-01-foundations). +- HTTP-pipeline OTel instrumentation that produces transport metrics → [Chapter 02 — aspnetcore](#chapter-02-aspnetcore). +- EF Core query-shape choices that dominate allocations → [Chapter 03 — data](#chapter-03-data). +- Microbenchmark fixtures vs integration perf tests → [Chapter 04 — testing](#chapter-04-testing). +- OTLP exporters, collector, and K8s CPU/QoS that the metrics are read from → [Chapter 06 — cloud-native](#chapter-06-cloud-native). ### Cross-links -- App metrics meters owned here ↔ OTLP exporters in [06](#chapter-06--cloud-native). -- Container env-vars owned here ↔ Dockerfile baseline in [06](#chapter-06--cloud-native). -- NativeAOT owned here ↔ source generators in [01](#chapter-01--foundations). +- App metrics meters owned here ↔ OTLP exporters in [06](#chapter-06-cloud-native). +- Container env-vars owned here ↔ Dockerfile baseline in [06](#chapter-06-cloud-native). +- NativeAOT owned here ↔ source generators in [01](#chapter-01-foundations). --- @@ -199,8 +199,8 @@ Source: [`docs/06-cloud-native.md`](./docs/06-cloud-native.md). - .NET Aspire AppHost composition and client integrations — **Aspire 9.x** for `net8.0` / `net9.0` services, **Aspire 13** for `net10.0` services (`Aspire.Hosting.*`, `Aspire.*`). - `ServiceDefaults` shared project (§1): OTel wiring, default health checks, resilience handlers, service discovery defaults. - **Configuration in cluster** (§4): ConfigMap + CSI Key Vault driver as the configuration substrate; `appsettings.Production.json` rejected. Foundations §6 owns the in-process options pattern; this chapter owns where the bytes come from in the cluster. -- **Resilience pipeline defaults at the platform layer** (§6): Polly v8 standard-pipeline composition for non-HTTP resources (queues, blobs, repositories) and the rationale for keeping HTTP resilience in [02 §7](#chapter-02--aspnetcore). HTTP retry verb policy is owned by ch02; this chapter does not re-decide it. -- **Health-check probe contract** (§10): `/health/live`, `/health/ready`, `/health/startup` endpoint names, semantics, what each probe may do, and the K8s probe wiring — matches the Aspire `ServiceDefaults` mapping. Chapter 02 §18 only owns the in-process `MapHealthChecks` plumbing and links here for the contract. +- **Resilience pipeline defaults at the platform layer** (§6): Polly v8 standard-pipeline composition for non-HTTP resources (queues, blobs, repositories) and the rationale for keeping HTTP resilience in [02 §7](#chapter-02-aspnetcore). HTTP retry verb policy is owned by ch02; this chapter does not re-decide it. +- **Health-check probe contract** (§10): `/health/live`, `/health/ready`, `/health/startup` endpoint names, semantics, what each probe may do, and the K8s probe wiring. Aspire `ServiceDefaults` ships **only** `/health` + `/alive` and **only in Development** — production code must explicitly `MapHealthChecks` for `/health/live`, `/health/ready`, `/health/startup` (see [ch06 §10](#chapter-06-cloud-native) and the [aspire.dev ServiceDefaults reference](https://aspire.dev/fundamentals/service-defaults/)). Chapter 02 §18 only owns the in-process `MapHealthChecks` plumbing and links here for the contract. - Kubernetes runtime contract: liveness / readiness / startup probes, QoS class, the CPU-limits-considered-harmful stance, requests sizing. - Service discovery via `Microsoft.Extensions.ServiceDiscovery` and `HttpClient` integration (§7). - **Networking** (§13): HTTP/2 + HTTP/3 enablement, forwarded headers in cluster, mTLS via service mesh, TLS termination boundary. @@ -208,25 +208,25 @@ Source: [`docs/06-cloud-native.md`](./docs/06-cloud-native.md). - **Secrets & identity** (§8): Workload Identity + Federated Identity Credentials only — no client secrets, no pod-identity v1, no service-principal passwords on disk. - ASP.NET Core Data Protection key-ring storage (Azure Blob + Key Vault, or equivalent) for multi-replica deployments (§9). - **Graceful shutdown** (§11): `IHostApplicationLifetime`, `preStop` hook, drain order, K8s `terminationGracePeriodSeconds`. Chapter 02's background-work section composes against this contract. -- Outbox **dispatcher** topology (hosted service / sidecar / worker pool) — the pattern itself is owned by [03](#chapter-03--data). -- **CI/CD** (§12): reproducible image build, Sigstore/cosign signing, SBOM, scanning gate. Source-build hygiene is owned by [01 §11](#chapter-01--foundations); this chapter owns the cluster-deployment pipeline. +- Outbox **dispatcher** topology (hosted service / sidecar / worker pool) — the pattern itself is owned by [03](#chapter-03-data). +- **CI/CD** (§12): reproducible image build, Sigstore/cosign signing, SBOM, scanning gate. Source-build hygiene is owned by [01 §11](#chapter-01-foundations); this chapter owns the cluster-deployment pipeline. - **Multi-tenancy at runtime** (§14): tenant-scoped DI, per-tenant configuration / connection-string resolution, isolation expectations at the cluster boundary. - **Cost & efficiency** (§15): right-sizing requests, scale-to-zero suitability, image size baseline, the cluster-level levers a service team owns. -- Dockerfile baseline for .NET 10 images, including the runtime env-vars selected in [05](#chapter-05--performance). +- Dockerfile baseline for .NET 10 images, including the runtime env-vars selected in [05](#chapter-05-performance). ### References (does not re-decide) -- Logging primitives feeding the OTLP log exporter → [Chapter 01 — foundations](#chapter-01--foundations). -- Endpoint surface and OTel HTTP instrumentation → [Chapter 02 — aspnetcore](#chapter-02--aspnetcore). -- EF migration bundles invoked as init-containers / Jobs → [Chapter 03 — data](#chapter-03--data). -- `DistributedApplicationTestingBuilder` and integration-test wiring → [Chapter 04 — testing](#chapter-04--testing). -- GC and runtime env-vars copied into the Dockerfile → [Chapter 05 — performance](#chapter-05--performance). +- Logging primitives feeding the OTLP log exporter → [Chapter 01 — foundations](#chapter-01-foundations). +- Endpoint surface and OTel HTTP instrumentation → [Chapter 02 — aspnetcore](#chapter-02-aspnetcore). +- EF migration bundles invoked as init-containers / Jobs → [Chapter 03 — data](#chapter-03-data). +- `DistributedApplicationTestingBuilder` and integration-test wiring → [Chapter 04 — testing](#chapter-04-testing). +- GC and runtime env-vars copied into the Dockerfile → [Chapter 05 — performance](#chapter-05-performance). ### Cross-links -- Aspire `ServiceDefaults` owned here ↔ OTel HTTP wiring in [02](#chapter-02--aspnetcore) ↔ app metrics in [05](#chapter-05--performance). -- K8s probes owned here ↔ `IHostApplicationLifetime` / health checks contract in [01](#chapter-01--foundations). -- Data Protection key ring owned here ↔ cookie/antiforgery surfaces in [02](#chapter-02--aspnetcore) and [07](#chapter-07--client). +- Aspire `ServiceDefaults` owned here ↔ OTel HTTP wiring in [02](#chapter-02-aspnetcore) ↔ app metrics in [05](#chapter-05-performance). +- K8s probes owned here ↔ `IHostApplicationLifetime` / health checks contract in [01](#chapter-01-foundations). +- Data Protection key ring owned here ↔ cookie/antiforgery surfaces in [02](#chapter-02-aspnetcore) and [07](#chapter-07-client). --- @@ -246,16 +246,16 @@ Source: [`docs/07-client.md`](./docs/07-client.md). ### References (does not re-decide) -- DI lifetimes, NRT, async fundamentals as language baseline → [Chapter 01 — foundations](#chapter-01--foundations). -- JWT bearer + Entra token shapes the client consumes → [Chapter 02 — aspnetcore](#chapter-02--aspnetcore). -- `bUnit` / device-test plumbing → [Chapter 04 — testing](#chapter-04--testing). -- Data Protection key ring backing Blazor auth cookies in multi-replica → [Chapter 06 — cloud-native](#chapter-06--cloud-native). +- DI lifetimes, NRT, async fundamentals as language baseline → [Chapter 01 — foundations](#chapter-01-foundations). +- JWT bearer + Entra token shapes the client consumes → [Chapter 02 — aspnetcore](#chapter-02-aspnetcore). +- `bUnit` / device-test plumbing → [Chapter 04 — testing](#chapter-04-testing). +- Data Protection key ring backing Blazor auth cookies in multi-replica → [Chapter 06 — cloud-native](#chapter-06-cloud-native). ### Cross-links -- Auth-state serialization owned here ↔ JWT/Entra validation in [02](#chapter-02--aspnetcore). -- Antiforgery on `EditForm` owned here ↔ antiforgery middleware in [02](#chapter-02--aspnetcore). -- Render-mode decision table owned here ↔ Data Protection key ring in [06](#chapter-06--cloud-native). +- Auth-state serialization owned here ↔ JWT/Entra validation in [02](#chapter-02-aspnetcore). +- Antiforgery on `EditForm` owned here ↔ antiforgery middleware in [02](#chapter-02-aspnetcore). +- Render-mode decision table owned here ↔ Data Protection key ring in [06](#chapter-06-cloud-native). --- @@ -272,13 +272,13 @@ Sources: [`patterns/anti-patterns.md`](./patterns/anti-patterns.md), [`patterns/ ### References (does not re-decide) -- Subsystem-specific anti-patterns stay in their owning chapter (e.g., EF tracking misuse stays in [03](#chapter-03--data); captured-async pitfalls stay in [01](#chapter-01--foundations)). +- Subsystem-specific anti-patterns stay in their owning chapter (e.g., EF tracking misuse stays in [03](#chapter-03-data); captured-async pitfalls stay in [01](#chapter-01-foundations)). - Per-claim citations for chapter-level rules stay inline in the owning chapter, not in `references.md`. ### Cross-links - `patterns/patterns.md` ↔ chapters 01–07 (each named pattern points back to its owning chapter for the binding rule). -- `patterns/monorepo.md` ↔ [04 — testing](#chapter-04--testing) for fan-out test selection and [06 — cloud-native](#chapter-06--cloud-native) for image-build topology. +- `patterns/monorepo.md` ↔ [04 — testing](#chapter-04-testing) for fan-out test selection and [06 — cloud-native](#chapter-06-cloud-native) for image-build topology. --- diff --git a/docs/01-foundations.md b/docs/01-foundations.md index cca6a21..9ed9d55 100644 --- a/docs/01-foundations.md +++ b/docs/01-foundations.md @@ -3,7 +3,7 @@ Opinionated, cross-cutting defaults for a .NET 10 codebase. Everything here is do-this-by-default; deviations should be justified in code review. ASP.NET Core surface lives in [chapter 02](./02-aspnetcore.md), data in [chapter 03](./03-data.md), testing in [chapter 04](./04-testing.md), perf in [chapter 05](./05-performance.md), cloud-native in [chapter 06](./06-cloud-native.md), client in [chapter 07](./07-client.md). -Ownership of each concept (and what defers where) is recorded in [`coverage-map.md`](../coverage-map.md#chapter-01--foundations). +Ownership of each concept (and what defers where) is recorded in [`coverage-map.md`](../coverage-map.md#chapter-01-foundations). > Conventions: **Do** = required default. **Don't** = reject in review unless a > written exception exists. **Prefer** = strong default; use the alternative @@ -96,6 +96,8 @@ libs; you will want it the first time prod stack-traces hit a NuGet'd package. - [`ContinuousIntegrationBuild`](https://learn.microsoft.com/dotnet/core/project-sdk/msbuild-props#continuousintegrationbuild) — what the CI flag strips from PDBs. - [reproducible-builds.org — Definition](https://reproducible-builds.org/docs/definition/) — vendor-neutral definition the .NET reproducible-build flags target. - [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html) — the contract `Directory.Packages.props` versions are expected to honor. +- [`dotnet/sdk` — repository](https://github.com/dotnet/sdk) — primary source for SDK behaviour, MSBuild SDK targets, and `global.json` semantics. +- [ECMA-334 — C# Language Specification](https://ecma-international.org/publications-and-standards/standards/ecma-334/) — the standardized language spec the C# compiler targets. > **See also**: monorepo layout and CI fan-out live in [`patterns/monorepo.md`](../patterns/monorepo.md), and the test-project conventions assumed by the `tests/` sibling layout are owned by [chapter 04 §1–§2](./04-testing.md#1-the-pyramid-and-why-most-teams-get-it-wrong). @@ -250,6 +252,10 @@ method does. - [Pattern matching](https://learn.microsoft.com/dotnet/csharp/fundamentals/functional/pattern-matching) — switch expressions, property/list patterns. - [`ref readonly` parameters](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/ref#ref-readonly-parameters) — when to use over `in`. - [ECMA-335 — Common Language Infrastructure](https://ecma-international.org/publications-and-standards/standards/ecma-335/) — the standardized runtime/IL spec the language targets (vendor-neutral baseline behind the C# spec). +- [`dotnet/csharplang` — language design notes](https://github.com/dotnet/csharplang) — primary source for C# 13/14 design discussions, LDM notes, and proposal status (the spec ships from here). +- [`dotnet/csharplang` — C# 14 proposals](https://github.com/dotnet/csharplang/tree/main/proposals/csharp-14.0) — `field` keyword, extension members, partial constructors, first-class Span conversions: where the design is recorded. +- [`dotnet/csharplang` — C# 13 proposals](https://github.com/dotnet/csharplang/tree/main/proposals/csharp-13.0) — `params` collections, `ref readonly`, `Lock` type, `OverloadResolutionPriority`. +- [`dotnet/roslyn` — repository](https://github.com/dotnet/roslyn) — the C# compiler; primary source for analyzer/source-generator authoring and language-feature implementation status. --- @@ -302,6 +308,8 @@ amnesty. - [`ArgumentNullException.ThrowIfNull`](https://learn.microsoft.com/dotnet/api/system.argumentnullexception.throwifnull) — annotated guard; feeds flow analysis. - [`StringSyntaxAttribute`](https://learn.microsoft.com/dotnet/api/system.diagnostics.codeanalysis.stringsyntaxattribute) — string-literal hints for analyzers/IDEs. - [Andrew Lock — *Embracing nullable reference types*](https://andrewlock.net/series/embracing-nullable-reference-types/) — community-canonical migration playbook for incremental NRT adoption on existing codebases. +- [`dotnet/csharplang` — Nullable reference types specification](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/nullable-reference-types-specification.md) — primary spec for NRT flow analysis, suppressions, and attribute semantics. +- [Mads Torgersen — *Embracing nullable reference types* (devblogs)](https://devblogs.microsoft.com/dotnet/embracing-nullable-reference-types/) — language-team announcement and rationale. --- @@ -390,6 +398,8 @@ singleton — the captured state is the lifetime of the singleton. - [Stephen Cleary — *Async and Await*](https://blog.stephencleary.com/2012/02/async-and-await.html) — canonical primer; `async void` exception flow. - [Stephen Cleary — *Don't Block on Async Code*](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html) — sync-over-async deadlock anatomy. - [David Fowler — *AspNetCoreDiagnosticScenarios — AsyncGuidance*](https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/main/AsyncGuidance.md) — community-maintained field guide to async + DI gotchas; cross-linked from `Further reading`. +- [Stephen Toub — *Async/await internals* (devblogs)](https://devblogs.microsoft.com/dotnet/how-async-await-really-works/) — primary-source walk-through of the state machine, `IValueTaskSource`, and async-method-builder lowering. +- [`dotnet/runtime` — Threading & Tasks design docs](https://github.com/dotnet/runtime/tree/main/docs/design/features) — primary source for `Task`, `ValueTask`, and synchronization-context internals. --- @@ -454,7 +464,7 @@ never re-resolves). Add resilience via `Microsoft.Extensions.Http.Resilience` (Polly v8 under the hood): `.AddStandardResilienceHandler()` gets you retry+circuit-breaker+timeout defaults that are sensible. -The cluster-level resilience story (Aspire `ServiceDefaults` wiring of the same handler across every outbound `HttpClient`) is owned by [chapter 06 §6 Resilience](./06-cloud-native.md#6-resilience--polly-v8-standard-pipelines-not-hand-rolled-retries); this section owns the per-client defaults, that section owns the platform default. +The cluster-level resilience story (Aspire `ServiceDefaults` wiring of the same handler across every outbound `HttpClient`) is owned by [chapter 06 §6 Resilience](./06-cloud-native.md#6-resilience-see-ch02-7); this section owns the per-client defaults, that section owns the platform default. **Sources:** @@ -466,6 +476,7 @@ The cluster-level resilience story (Aspire `ServiceDefaults` wiring of the same - [`IHttpClientFactory`](https://learn.microsoft.com/dotnet/core/extensions/httpclient-factory) — named/typed clients, handler pooling. - [`Microsoft.Extensions.Http.Resilience`](https://learn.microsoft.com/dotnet/core/resilience/http-resilience) — Polly v8 standard handlers. - [Steve Gordon — *HttpClientFactory in ASP.NET Core* (series)](https://www.stevejgordon.co.uk/introduction-to-httpclientfactory-aspnetcore) — community-canonical internals walkthrough: handler pooling, lifetimes, the `LogicalHandler`/`PrimaryHandler` chain. +- [`dotnet/runtime` — `Microsoft.Extensions.DependencyInjection` source](https://github.com/dotnet/runtime/tree/main/src/libraries/Microsoft.Extensions.DependencyInjection) — primary source for DI lifetime semantics, scope validation, and keyed services. --- @@ -503,7 +514,7 @@ environment variables → command-line. Don't fight the precedence; document it. process and shell history. Use `dotnet user-secrets` for local dev. - In every other environment, use Key Vault (or your cloud equivalent) and reference via the configuration provider; the provider handles rotation. - Cluster-side secrets topology (Workload Identity, CSI Key Vault driver, ConfigMap layering) is owned by [chapter 06 §4 Configuration](./06-cloud-native.md#4-configuration--configmap--csi-key-vault-not-appsettingsproductionjson) and [chapter 06 §8 Secrets & identity](./06-cloud-native.md#8-secrets--identity--workload-identity-only); this section owns only the in-process binding/validation rules. + Cluster-side secrets topology (Workload Identity, CSI Key Vault driver, ConfigMap layering) is owned by [chapter 06 §4 Configuration](./06-cloud-native.md#4-configuration-configmap-csi-key-vault-not-appsettingsproductionjson) and [chapter 06 §8 Secrets & identity](./06-cloud-native.md#8-secrets-identity-workload-identity-only); this section owns only the in-process binding/validation rules. - `ILogger` redaction: ensure secrets are never tokens in structured log templates; wrap in a custom type that overrides `ToString()`. @@ -518,6 +529,7 @@ smell that defeats validation, defeats reload, and defeats testability. - [Configuration providers & precedence](https://learn.microsoft.com/dotnet/core/extensions/configuration) — layering rules. - [Safe storage of secrets in development](https://learn.microsoft.com/aspnet/core/security/app-secrets) — `dotnet user-secrets`. - [Andrew Lock — *Adding validation to strongly-typed configuration objects*](https://andrewlock.net/adding-validation-to-strongly-typed-configuration-objects-in-asp-net-core/) — community-canonical walk-through of `IValidateOptions`, `[OptionsValidator]`, and `ValidateOnStart` patterns. +- [`[OptionsValidator]` source generator — devblogs](https://devblogs.microsoft.com/dotnet/announcing-dotnet-8-rc1/#optionsvalidator-source-generator) — the .NET team announcement and rationale for compile-time options validation. --- @@ -550,6 +562,7 @@ always `using`. - [Implement `IDisposable` / dispose pattern](https://learn.microsoft.com/dotnet/standard/garbage-collection/implementing-dispose) — when finalizers, when not. - [Implement `IAsyncDisposable`](https://learn.microsoft.com/dotnet/standard/garbage-collection/implementing-disposeasync) — `await using` rules. - [`CancellationTokenSource`](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtokensource) — disposal of linked sources. +- [Stephen Toub — *DisposeAsync* notes (devblogs)](https://devblogs.microsoft.com/dotnet/configureawait-faq/#what-about-asynchronous-disposal) — async disposal semantics and `await using` rules from the libraries lead. --- @@ -560,7 +573,7 @@ exceptional — return them as values (model-state errors, `ProblemDetails`, `Result`). Authorization failures, missing-but-expected resources, optimistic concurrency, downstream timeouts — all *expected* on a healthy system; design them as flow, not throws. -The HTTP error envelope (`ProblemDetails` per RFC 9457, `AddProblemDetails`, exception-handler middleware) is owned by [chapter 02 §3 ProblemDetails & Error Handling](./02-aspnetcore.md#3-problemdetails--error-handling); this section owns only the in-process exception discipline. +The HTTP error envelope (`ProblemDetails` per RFC 9457, `AddProblemDetails`, exception-handler middleware) is owned by [chapter 02 §3 ProblemDetails & Error Handling](./02-aspnetcore.md#3-problemdetails-error-handling); this section owns only the in-process exception discipline. **Don't catch `Exception`.** Two exceptions: (1) a top-level boundary that logs and converts to a transport error (ASP.NET exception handler middleware, @@ -599,6 +612,8 @@ catch it specifically, it shouldn't exist as its own type — a built-in - [Best practices for exceptions](https://learn.microsoft.com/dotnet/standard/exceptions/best-practices-for-exceptions) — design rules. - [`ExceptionDispatchInfo`](https://learn.microsoft.com/dotnet/api/system.runtime.exceptionservices.exceptiondispatchinfo) — preserving stacks across boundaries. - [`ProblemDetails` (RFC 9457)](https://datatracker.ietf.org/doc/html/rfc9457) — current standard error envelope for HTTP APIs; obsoletes RFC 7807. +- [RFC 9110 — HTTP Semantics](https://datatracker.ietf.org/doc/html/rfc9110) — primary source for status-code semantics that shape the exception → ProblemDetails mapping. +- [`dotnet/runtime` — Exception design notes](https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/botr/exceptions.md) — primary-source CLR exception model behind the BCL guidance. --- @@ -654,6 +669,8 @@ This is the canonical C# 14 replacement for the `private string _name; public st - [`System.Collections.Immutable`](https://learn.microsoft.com/dotnet/api/system.collections.immutable) — `ImmutableArray`, `ImmutableList`. - [Choosing between class and struct](https://learn.microsoft.com/dotnet/standard/design-guidelines/choosing-between-class-and-struct) — when value semantics fit. - [Eric Lippert — *Immutability in C#* (series)](https://learn.microsoft.com/archive/blogs/ericlippert/immutability-in-c-part-one-kinds-of-immutability) — taxonomy of write-once / shallow / deep / observational immutability; shapes the rule that `IReadOnlyList` is a view, not a guarantee. +- [`dotnet/csharplang` — Records proposal](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/records.md) — primary-source design notes for `record` value-equality semantics referenced throughout this section. +- [Mads Torgersen — *C# 9 Records* (devblogs)](https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/) — language-team announcement of records and `init` accessors. --- @@ -681,7 +698,7 @@ default; fall back to reflection only when shape isn't statically knowable. - **Minimal API `RequestDelegate` generator** (on by default in ASP.NET Core 7+). Removes per-request reflection; you get it for free as long as your endpoint signatures are static. Don't dynamically build endpoints if you - can express them statically — see [chapter 02 §1 Minimal APIs vs Controllers](./02-aspnetcore.md#1-minimal-apis-vs-controllers) for the endpoint-shape rules and [chapter 05 §6 Reflection alternatives](./05-performance.md#6-reflection-alternatives--source-generators-win) for the perf rationale that puts generators ahead of reflection by default. + can express them statically — see [chapter 02 §1 Minimal APIs vs Controllers](./02-aspnetcore.md#1-minimal-apis-vs-controllers) for the endpoint-shape rules and [chapter 05 §6 Reflection alternatives](./05-performance.md#6-reflection-alternatives-source-generators-win) for the perf rationale that puts generators ahead of reflection by default. `StringSyntaxAttribute` is *not* a source generator — it's a metadata attribute consumed by analyzers/IDEs; see §3. @@ -705,6 +722,8 @@ runs in the IDE — every dep becomes a load-order hazard). - [`System.Text.Json` source generation](https://learn.microsoft.com/dotnet/standard/serialization/system-text-json/source-generation) — AOT, perf, startup. - [`GeneratedRegex`](https://learn.microsoft.com/dotnet/standard/base-types/regular-expression-source-generators) — compile-time regex. - [Roslyn interceptors feature doc](https://github.com/dotnet/roslyn/blob/main/docs/features/interceptors.md) — current status, encoding, generator-only guidance. +- [`dotnet/runtime` — `LoggerMessage` source generator](https://github.com/dotnet/runtime/tree/main/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen) — primary-source generator implementation behind the high-perf logging guidance. +- [Source generators cookbook (`dotnet/roslyn`)](https://github.com/dotnet/roslyn/blob/main/docs/features/source-generators.cookbook.md) — primary-source authoring guide. - [Source generators overview](https://learn.microsoft.com/dotnet/csharp/roslyn-sdk/source-generators-overview) — authoring guidance. --- @@ -758,7 +777,7 @@ server (or NuGet.org's). **`dotnet test` and Microsoft.Testing.Platform.** The .NET 10 SDK ships first-class Microsoft.Testing.Platform (MTP) support in `dotnet test` alongside the legacy VSTest pipeline. -The runner choice, opt-in, and CI integration rules are owned by [chapter 04 §2 Frameworks: xUnit v3, MSTest, NUnit](./04-testing.md#2-frameworks-xunit-v3-mstest-nunit); just don't bake VSTest-only assumptions into CI templates you author today. +The runner choice, opt-in, and CI integration rules are owned by [chapter 04 §2 Frameworks: xUnit v3, MSTest, NUnit](./04-testing.md#2-frameworks-xunit-v3-mstest-nunit-tunit); just don't bake VSTest-only assumptions into CI templates you author today. **Sources:** @@ -770,6 +789,7 @@ The runner choice, opt-in, and CI integration rules are owned by [chapter 04 §2 - [.NET 10 SDK — what's new](https://learn.microsoft.com/dotnet/core/whats-new/dotnet-10/sdk) — `dotnet test` and Microsoft.Testing.Platform. - [OWASP — Software Component Verification Standard (SCVS)](https://owasp.org/www-project-software-component-verification-standard/) — vendor-neutral baseline that `NuGetAudit` + lock-file restore are meant to satisfy. - [NIST SP 800-218 — Secure Software Development Framework (SSDF)](https://csrc.nist.gov/publications/detail/sp/800-218/final) — primary-source reference for the supply-chain controls (PW.4, PS.3) the build pipeline implements. +- [Sigstore project](https://www.sigstore.dev/) — primary-source supply-chain signing infrastructure (`cosign`, transparency log) referenced from the CI baseline. --- @@ -778,7 +798,7 @@ The runner choice, opt-in, and CI integration rules are owned by [chapter 04 §2 The defaults are good. Change them only with measurement, and pin the overrides in source so they ride with the binary instead of living in a deployment script. -NativeAOT specifics, `DOTNET_*` Dockerfile env-vars, and `GCHeapCount` tuning are owned by [chapter 05 §8 JIT & AOT](./05-performance.md#8-jit--aot), [chapter 05 §9 GC](./05-performance.md#9-gc), and [chapter 05 §11 Containers](./05-performance.md#11-containers--what-to-set); the container baseline that consumes them is owned by [chapter 06 §2 Containerization](./06-cloud-native.md#2-containerization--no-dockerfile-if-you-can-avoid-it) and [chapter 06 §3 Kubernetes / AKS](./06-cloud-native.md#3-kubernetes--aks--probes-limits-gc-shutdown). +NativeAOT specifics, `DOTNET_*` Dockerfile env-vars, and `GCHeapCount` tuning are owned by [chapter 05 §8 JIT & AOT](./05-performance.md#8-jit-aot), [chapter 05 §9 GC](./05-performance.md#9-gc), and [chapter 05 §11 Containers](./05-performance.md#11-containers-what-to-set); the container baseline that consumes them is owned by [chapter 06 §2 Containerization](./06-cloud-native.md#2-containerization-no-dockerfile-if-you-can-avoid-it) and [chapter 06 §3 Kubernetes / AKS](./06-cloud-native.md#3-kubernetes-aks-probes-limits-gc-shutdown). This section owns only the in-process, in-csproj defaults. **Server GC for server workloads.** ASP.NET Core and most service hosts @@ -860,6 +880,8 @@ next to the csproj; do not let ops set GC policy in the deploy pipeline. - [CET — Control-flow Enforcement Technology (.NET 9)](https://learn.microsoft.com/dotnet/core/whats-new/dotnet-9/runtime#control-flow-enforcement-technology) — default-on shadow stack on Windows. - [Tiered compilation](https://learn.microsoft.com/dotnet/core/runtime-config/compilation) — how tiers and quick-JIT interact. - [Performance improvements in .NET 10 — Stephen Toub](https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-10/) — current canonical perf post; supersedes the .NET 8 link for Dynamic PGO. +- [`dotnet/runtime` — GC documentation](https://github.com/dotnet/runtime/tree/main/docs/design/coreclr/botr) — primary-source GC design notes (Server vs Workstation, DATAS, write barriers). +- [`dotnet/runtime` — Tiered compilation / Dynamic PGO design](https://github.com/dotnet/runtime/blob/main/docs/design/features/TieredCompilation.md) — primary source for tier-0/tier-1 behaviour referenced in this section. - [PGO improvements: type checks and casts (.NET 9)](https://learn.microsoft.com/dotnet/core/whats-new/dotnet-9/runtime#pgo-improvements-type-checks-and-casts) — what Dynamic PGO learned in .NET 9. - [Thread-pool runtime config](https://learn.microsoft.com/dotnet/core/runtime-config/threading) — `MinThreads`, hill-climbing semantics. - [`RuntimeHostConfigurationOption` MSBuild item](https://learn.microsoft.com/dotnet/core/runtime-config/#specify-a-configuration-value) — how project switches reach `runtimeconfig.json`. diff --git a/docs/02-aspnetcore.md b/docs/02-aspnetcore.md index b3718a8..5356eb0 100644 --- a/docs/02-aspnetcore.md +++ b/docs/02-aspnetcore.md @@ -934,7 +934,6 @@ Sources: - [Andrew Lock — andrewlock.net](https://andrewlock.net/) — deep, careful dives on middleware, auth, configuration, source generators. The reference blog for ASP.NET Core internals. - [David Fowler — gists and talks](https://gist.github.com/davidfowl) — especially [davidfowl/AspNetCoreDiagnosticScenarios](https://github.com/davidfowl/AspNetCoreDiagnosticScenarios): async/await, `HttpClient`, and threading traps straight from the architect. - [Khalid Abuhakmeh — khalidabuhakmeh.com](https://khalidabuhakmeh.com/) — pragmatic tips, Minimal APIs, tooling. -- [Nick Chapsas — YouTube](https://www.youtube.com/@nickchapsas) — current .NET features explained quickly; good for "what changed in the latest preview". - [Steve Gordon — stevejgordon.co.uk](https://www.stevejgordon.co.uk/) and NDC talks — `HttpClientFactory` internals, performance, diagnostics. - [Jimmy Bogard — jimmybogard.com](https://www.jimmybogard.com/) — domain modeling, MediatR, vertical slice architecture, ProblemDetails patterns. - [Gérald Barré (Meziantou) — meziantou.net](https://www.meziantou.net/) — edge cases, analyzers, source generators, niche perf. diff --git a/docs/03-data.md b/docs/03-data.md index 374babf..134cf0a 100644 --- a/docs/03-data.md +++ b/docs/03-data.md @@ -671,7 +671,7 @@ Sources: - Andrew Lock — https://andrewlock.net/ (config, startup, DbContext lifetime, HybridCache) - Jimmy Bogard — https://www.jimmybogard.com/ (outbox, CQRS, repository critique) - Khalid Abuhakmeh — https://khalidabuhakmeh.com/ (EF + Dapper interop) -- Nick Chapsas — https://nickchapsas.com/ and YouTube (perf, Dapper vs EF benchmarks) +- Nick Chapsas — https://nickchapsas.com/ (perf, Dapper vs EF benchmarks) - Meziantou — https://www.meziantou.net/ (async, Data Protection, column encryption) - Steve Gordon — https://www.stevejgordon.co.uk/ (async, caching, HybridCache internals) - David Fowler — gists on `TransactionScope`, async pitfalls — https://gist.github.com/davidfowl diff --git a/docs/04-testing.md b/docs/04-testing.md index 79214fa..b233608 100644 --- a/docs/04-testing.md +++ b/docs/04-testing.md @@ -804,7 +804,6 @@ Sources: ### Community - Khalid Abuhakmeh — testing & ASP.NET Core posts — https://khalidabuhakmeh.com/ - Andrew Lock — *.NET Escapades* (integration tests, WebApplicationFactory, Testcontainers) — https://andrewlock.net/ -- Nick Chapsas — YouTube deep-dives on xUnit v3, NSubstitute, Verify — https://www.youtube.com/@nickchapsas - Jimmy Bogard — *Los Techies* / blog (integration testing, Respawn author) — https://www.jimmybogard.com/ - Steve Smith (Ardalis) — testing & clean architecture — https://ardalis.com/ - Bradley Wells — xUnit v3 / MTP migration guides — https://bradwells.com/ (and https://wellsb.com) diff --git a/docs/05-performance.md b/docs/05-performance.md index 07500af..c819eb8 100644 --- a/docs/05-performance.md +++ b/docs/05-performance.md @@ -9,7 +9,7 @@ Dense, opinionated, sourced. Targets **.NET 10 (LTS, Nov 2025)**. Assumes ASP.NE - **Default benchmarking framework: BenchmarkDotNet.** No fallback. Everything else is BenchmarkDotNet with bugs — `Stopwatch` loops, custom harnesses, `xUnit` "perf tests" all skip warmup, JIT tier promotion, and statistical analysis. See §1 and §15. - **Default live-process profiler: `dotnet-trace`.** Cross-platform, sampled, ships in the SDK toolset, feeds PerfView and Speedscope. **Fallback: PerfView on Windows** when you need full ETW depth; **JetBrains dotTrace/dotMemory** when a GUI is non-negotiable for the audience. See §1. - **Default container GC mode: Server GC + concurrent + DATAS.** ASP.NET Core's host turns Server GC on; .NET 9+ makes DATAS the default Server-GC sub-mode. **Fallback: Workstation GC** — and the runtime forces it for you when the container sees `< 1` logical CPU, regardless of config. See §9 and §11. -- **Default container base image: `mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled`.** Distroless, non-root, ~100 MB. **Fallback: `azurelinux`** when you need a Microsoft-supported distro with a shell for debugging. See §11 and [Chapter 06 §2 — Containerization](./06-cloud-native.md#2-containerization--no-dockerfile-if-you-can-avoid-it). +- **Default container base image: `mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled`.** Distroless, non-root, ~100 MB. **Fallback: `azurelinux`** when you need a Microsoft-supported distro with a shell for debugging. See §11 and [Chapter 06 §2 — Containerization](./06-cloud-native.md#2-containerization-no-dockerfile-if-you-can-avoid-it). - **Runtime knobs that belong here, not scattered:** GC mode, DATAS, tiered compilation, dynamic PGO, ReadyToRun, NativeAOT, container env-vars (`DOTNET_*`). The *foundational* runtime-config surface — how to set them per-process via `runtimeconfig.json` / `` / env-vars — is owned by [Chapter 01 §12 — Runtime configuration](./01-foundations.md#12-runtime-configuration); this chapter decides *which values* to ship. --- @@ -277,14 +277,15 @@ Reflection is allocation, slow startup, and AOT-hostile. Most reasons to use it **Sources:** - Konrad Kokosa, *Pro .NET Memory Management* (Apress, 2018, ISBN 978-1-4842-4026-7) — https://prodotnetmemory.com/ — the definitive book on the runtime memory model, GC heap layout, and pause-time analysis. -- Maoni Stephens (.NET GC architect) on GC internals — https://maoni0.medium.com/ +- Maoni Stephens (.NET GC architect) on GC internals — https://devblogs.microsoft.com/dotnet/author/maoni/ +- dotnet/runtime — GC design docs — https://github.com/dotnet/runtime/tree/main/docs/design/coreclr/botr (see `garbage-collection.md`). - Stephen Toub, "Performance Improvements in .NET 9" (DATAS explainer) — https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-9/ - GC fundamentals: https://learn.microsoft.com/dotnet/standard/garbage-collection/fundamentals - Workstation vs Server GC (defaults, single-CPU rule): https://learn.microsoft.com/dotnet/standard/garbage-collection/workstation-server-gc - Latency modes: https://learn.microsoft.com/dotnet/standard/garbage-collection/latency - GC configuration knobs (`Server`, `DynamicAdaptationMode`, `HeapHardLimit*`, `RetainVM`): https://learn.microsoft.com/dotnet/core/runtime-config/garbage-collector - Process-level runtime config wiring for these knobs → [Chapter 01 §12 — Runtime configuration](./01-foundations.md#12-runtime-configuration). -- Pod-level memory requests/limits and QoS class that the GC reads → [Chapter 06 §3 — Kubernetes / AKS](./06-cloud-native.md#3-kubernetes--aks--probes-limits-gc-shutdown). +- Pod-level memory requests/limits and QoS class that the GC reads → [Chapter 06 §3 — Kubernetes / AKS](./06-cloud-native.md#3-kubernetes-aks-probes-limits-gc-shutdown). --- @@ -320,7 +321,7 @@ Reflection is allocation, slow startup, and AOT-hostile. Most reasons to use it There is **no universal "perf-tuned" env-var set**. The runtime defaults are good. Each tunable below moves a specific axis (startup vs steady-state vs memory) and can regress the others. Decide per service, then bake into the image. (**Env-var prefix:** `DOTNET_` is the standardized prefix since .NET 6; the old `COMPlus_` names still work but new code should use `DOTNET_`.) -**Default base image, decided here:** **`mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled`** — distroless, non-root (UID 1654), ~100 MB, no shell, smaller CVE surface. **Fallback: `azurelinux`** when you need a Microsoft-supported distro that includes a shell for in-container debugging; **`alpine`** only when image size dominates and you've audited native interop for musl. The Dockerfile baseline that wires this image into a multi-stage SDK build belongs to [Chapter 06 §2 — Containerization](./06-cloud-native.md#2-containerization--no-dockerfile-if-you-can-avoid-it); this chapter only picks the runtime knobs that go into the `ENV` lines. +**Default base image, decided here:** **`mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled`** — distroless, non-root (UID 1654), ~100 MB, no shell, smaller CVE surface. **Fallback: `azurelinux`** when you need a Microsoft-supported distro that includes a shell for in-container debugging; **`alpine`** only when image size dominates and you've audited native interop for musl. The Dockerfile baseline that wires this image into a multi-stage SDK build belongs to [Chapter 06 §2 — Containerization](./06-cloud-native.md#2-containerization-no-dockerfile-if-you-can-avoid-it); this chapter only picks the runtime knobs that go into the `ENV` lines. **Minimal Dockerfile (no tuning, just sane base):** @@ -375,8 +376,8 @@ ENV DOTNET_GCHeapHardLimitPercent=75 - SDK container publish: https://learn.microsoft.com/dotnet/core/docker/publish-as-container - Adam Sitnik on container env-var benchmarking — https://adamsitnik.com/ - How to *set* these env-vars per host (`runtimeconfig.json`, ``, `ENV` precedence) → [Chapter 01 §12 — Runtime configuration](./01-foundations.md#12-runtime-configuration). -- Dockerfile / chiseled-image baseline that consumes these env-vars → [Chapter 06 §2 — Containerization](./06-cloud-native.md#2-containerization--no-dockerfile-if-you-can-avoid-it). -- Pod sizing, QoS class, and the `cpu` requests/limits that the GC reads → [Chapter 06 §3 — Kubernetes / AKS](./06-cloud-native.md#3-kubernetes--aks--probes-limits-gc-shutdown). +- Dockerfile / chiseled-image baseline that consumes these env-vars → [Chapter 06 §2 — Containerization](./06-cloud-native.md#2-containerization-no-dockerfile-if-you-can-avoid-it). +- Pod sizing, QoS class, and the `cpu` requests/limits that the GC reads → [Chapter 06 §3 — Kubernetes / AKS](./06-cloud-native.md#3-kubernetes-aks-probes-limits-gc-shutdown). --- @@ -407,7 +408,7 @@ ENV DOTNET_GCHeapHardLimitPercent=75 **Sources:** - `LoggerMessage` source generator: https://learn.microsoft.com/dotnet/core/extensions/logger-message-generator - High-performance logging: https://learn.microsoft.com/dotnet/core/extensions/high-performance-logging -- Logging primitives, `ILogger`, and source-generated loggers as the language baseline → [Chapter 01 — foundations](./01-foundations.md#chapter-01--foundations). +- Logging primitives, `ILogger`, and source-generated loggers as the language baseline → [Chapter 01 — foundations §10 Source generators](./01-foundations.md#10-source-generators) (chapter-level ownership in [`coverage-map.md`](../coverage-map.md#chapter-01-foundations)). --- @@ -544,16 +545,20 @@ BDN measures *steady-state*. Startup wins from R2R, NativeAOT, and source genera - **Diagnostic CLI tools** (`dotnet-counters`, `dotnet-trace`, `dotnet-dump`, `dotnet-gcdump`). https://learn.microsoft.com/dotnet/core/diagnostics/ - **PerfView**. https://github.com/microsoft/perfview -### Community / people to follow +--- + +## Further reading (non-normative) + +The list below is community/practitioner pointers, not part of the canonical `Sources:` block above. Use them for depth, war stories, and benchmarks; rules in the chapter body still come from the authoritative sources. + - **Stephen Toub** (.NET libraries lead) — devblogs posts above; the source of truth. - **Adam Sitnik** (BenchmarkDotNet maintainer, .NET perf team) — https://adamsitnik.com/ — pooling, BDN, micro-opt patterns. - **Andrey Akinshin** (BenchmarkDotNet lead maintainer) — https://aakinshin.net/ — author of *Pro .NET Benchmarking: The Art of Performance Measurement* (Apress, 2019, ISBN 978-1-4842-4940-6); the reference on BDN methodology, statistical analysis, and benchmark pitfalls. - **Ben Adams** (Illyriad Games / .NET community) — https://github.com/benaadams — real-world high-throughput .NET service perf, Kestrel/ASP.NET Core internals, allocation hunting. -- **Maoni Stephens** (.NET GC architect) — https://maoni0.medium.com/ — anything about GC internals. +- **Maoni Stephens** (.NET GC architect) — https://devblogs.microsoft.com/dotnet/author/maoni/ — anything about GC internals. - **Stephen Cleary** — https://blog.stephencleary.com/ — async / `Task` semantics, `ValueTask`, threading. Author of *Concurrency in C# Cookbook*. - **Andrew Lock** — https://andrewlock.net/ — ASP.NET Core internals, source generators, deployment. - **David Fowler** (.NET architect) — Twitter/X and dotnet/aspnetcore — Pipelines, channels, ASP.NET Core perf patterns. -- **Nick Chapsas** — YouTube *KeepCoding* — accessible perf walkthroughs and BenchmarkDotNet demos. - **Konrad Kokosa — *Pro .NET Memory Management*** (Apress, 2018, still the definitive book on the runtime memory model and GC). Companion site: https://prodotnetmemory.com/ - **Sasha Goldshtein**, **Ben Watson — *Writing High-Performance .NET Code*** (2nd ed.) — practical perf engineering reference. - **Marc Gravell** — protobuf-net, Pipelines, Dapper perf. diff --git a/docs/decision-trees.md b/docs/decision-trees.md index 61a108d..ed58f01 100644 --- a/docs/decision-trees.md +++ b/docs/decision-trees.md @@ -139,7 +139,7 @@ flowchart TD D -->|Mixed, want first paint fast
then offload| H[Interactive Auto
only if both modes supported — ch07] ``` -References: [`docs/07-client.md#part-1--blazor-unified-web-app-model`](./07-client.md#part-1--blazor-unified-web-app-model). +References: [`docs/07-client.md#part-1--blazor-unified-web-app-model`](./07-client.md#part-1-blazor-unified-web-app-model). --- @@ -167,7 +167,7 @@ flowchart TD D -->|One platform dominates,
platform-only APIs| I[Native SDK — ch07] ``` -References: [`docs/07-client.md#part-2--maui-net-10`](./07-client.md#part-2--maui-net-10). +References: [`docs/07-client.md#part-2--maui-net-10`](./07-client.md#part-2-maui-net-10). --- @@ -244,7 +244,7 @@ flowchart TD F -->|No| C ``` -References: [`docs/05-performance.md#8-jit--aot`](./05-performance.md#8-jit--aot). +References: [`docs/05-performance.md#8-jit--aot`](./05-performance.md#8-jit-aot). --- @@ -290,7 +290,7 @@ flowchart TD D -->|No, multiple endpoints / policies| C ``` -References: [`docs/01-foundations.md#5-di--lifetimes`](./01-foundations.md#5-di--lifetimes), [`docs/02-aspnetcore.md#11-httpclient`](./02-aspnetcore.md#11-httpclient). +References: [`docs/01-foundations.md#5-di--lifetimes`](./01-foundations.md#5-di-lifetimes), [`docs/02-aspnetcore.md#11-httpclient`](./02-aspnetcore.md#11-httpclient). --- @@ -332,19 +332,20 @@ References: [`docs/02-aspnetcore.md#10-authnauthz`](./02-aspnetcore.md#10-authna - Cost of wrong call: caching whole HTTP responses when you needed a domain object; rolling your own L1+L2 with stampede protection that HybridCache ships out of the box. -- Default per `ch03`: `OutputCache` for HTTP responses; `HybridCache` for - app-data with L1+L2 and stampede protection; `IDistributedCache` only as - a plain distributed K/V. +- Owner split: **`OutputCache` is owned by [`ch02 §9`](./02-aspnetcore.md#9-output-caching)** + (HTTP-response middleware). **`HybridCache` and `IDistributedCache` are + owned by [`ch03 §14`](./03-data.md#14-caching)** (data-shaped caches — + the matrix in ch03 §14 is for app data, not HTTP responses). ```mermaid flowchart TD A[Need a cache] --> B{What is being cached?} - B -->|Whole HTTP response
by route + vary| C[OutputCache — ch03] - B -->|Domain object / query result
multi-instance app| D[HybridCache
L1 in-proc + L2 distributed
+ stampede protection — ch03] - B -->|Plain distributed K/V
session, idempotency keys| E[IDistributedCache — ch03] + B -->|Whole HTTP response
by route + vary| C[OutputCache — ch02 §9] + B -->|Domain object / query result
multi-instance app| D[HybridCache
L1 in-proc + L2 distributed
+ stampede protection — ch03 §14] + B -->|Plain distributed K/V
session, idempotency keys| E[IDistributedCache — ch03 §14] ``` -References: [`docs/03-data.md#14-caching`](./03-data.md#14-caching), [`docs/02-aspnetcore.md#9-output-caching`](./02-aspnetcore.md#9-output-caching). +References: [`docs/02-aspnetcore.md#9-output-caching`](./02-aspnetcore.md#9-output-caching) (OutputCache owner), [`docs/03-data.md#14-caching`](./03-data.md#14-caching) (HybridCache / `IDistributedCache` owner). --- @@ -369,7 +370,7 @@ flowchart TD F -->|Yes| G[BestEffort — never in prod — ch06] ``` -References: [`docs/06-cloud-native.md#3-kubernetes--aks--probes-limits-gc-shutdown`](./06-cloud-native.md#3-kubernetes--aks--probes-limits-gc-shutdown). +References: [`docs/06-cloud-native.md#3-kubernetes--aks--probes-limits-gc-shutdown`](./06-cloud-native.md#3-kubernetes-aks-probes-limits-gc-shutdown). --- @@ -427,7 +428,7 @@ flowchart TD F -->|No| H[Configure connection from
cluster config / Key Vault — ch06 §4] ``` -References: [`docs/06-cloud-native.md#1-net-aspire--what-it-is-what-it-isnt`](./06-cloud-native.md#1-net-aspire--what-it-is-what-it-isnt). +References: [`docs/06-cloud-native.md#1-net-aspire--what-it-is-what-it-isnt`](./06-cloud-native.md#1-net-aspire-what-it-is-what-it-isnt). --- diff --git a/patterns/patterns.md b/patterns/patterns.md index b0e87cc..99d0528 100644 --- a/patterns/patterns.md +++ b/patterns/patterns.md @@ -11,9 +11,9 @@ Decision-bearing prose lives in the chapters; this page only points there. ## Foundations - **DI & lifetimes, keyed services** — Compose at the host, resolve via the container, prefer `GetRequiredKeyedService` over hand-rolled factories. - See [docs/01-foundations.md §5 DI & lifetimes](../docs/01-foundations.md#5-di--lifetimes). + See [docs/01-foundations.md §5 DI & lifetimes](../docs/01-foundations.md#5-di-lifetimes). - **Options pattern, validated, fail-fast** — Bind config sections to typed options, validate on start, prefer the `[OptionsValidator]` source generator. - See [docs/01-foundations.md §6 Configuration & options pattern](../docs/01-foundations.md#6-configuration--options-pattern). + See [docs/01-foundations.md §6 Configuration & options pattern](../docs/01-foundations.md#6-configuration-options-pattern). - **Source generators for hot paths** — `LoggerMessage`, `JsonSerializable`, `GeneratedRegex`, configuration binder, OpenAPI — all eliminate startup reflection and are AOT-safe. See [docs/01-foundations.md §10 Source generators](../docs/01-foundations.md#10-source-generators). - **`IAsyncDisposable` discipline** — Use `await using` for async resources; never mix sync `Dispose` with async resources or shutdown blocks. @@ -21,35 +21,35 @@ Decision-bearing prose lives in the chapters; this page only points there. - **Result types for expected failures** — Exceptions for *exceptional* (programmer error, infra failure), `Result` for *expected* (validation, business rules). See [docs/01-foundations.md §8 Exceptions](../docs/01-foundations.md#8-exceptions). - **Immutability & DTO design, value objects, strongly-typed IDs** — `record` for DTOs, `readonly record struct` for hot-path values, source-generated wrappers (Vogen / StronglyTypedId) for primitive obsession. - See [docs/01-foundations.md §9 Immutability & DTO design](../docs/01-foundations.md#9-immutability--dto-design). + See [docs/01-foundations.md §9 Immutability & DTO design](../docs/01-foundations.md#9-immutability-dto-design). - **`TimeProvider` in production code** — Inject `TimeProvider` (never `DateTime.UtcNow` directly) so handlers, schedulers, and token validation are testable. - See [docs/01-foundations.md §5 DI & lifetimes](../docs/01-foundations.md#5-di--lifetimes) and the testing counterpart [docs/04-testing.md §14 Time](../docs/04-testing.md#time--use-timeprovider-net-8). + See [docs/01-foundations.md §5 DI & lifetimes](../docs/01-foundations.md#5-di-lifetimes) and the testing counterpart [docs/04-testing.md §14 Time](../docs/04-testing.md#time-use-timeprovider-net-8). ## ASP.NET Core - **`ProblemDetails` + `IExceptionHandler`** — Standardise error responses via `AddProblemDetails`; chain multiple `IExceptionHandler`s instead of a single catch-all middleware. - See [docs/02-aspnetcore.md §3 ProblemDetails & Error Handling](../docs/02-aspnetcore.md#3-problemdetails--error-handling). + See [docs/02-aspnetcore.md §3 ProblemDetails & Error Handling](../docs/02-aspnetcore.md#3-problemdetails-error-handling). - **Resilience pipeline (Polly v8)** — Use the standard resilience handler on `HttpClient`; do not hand-roll retry/circuit-breaker loops. - See [docs/02-aspnetcore.md §7 Resilience](../docs/02-aspnetcore.md#7-resilience) and [docs/06-cloud-native.md §6 Resilience](../docs/06-cloud-native.md#6-resilience--polly-v8-standard-pipelines-not-hand-rolled-retries). + See [docs/02-aspnetcore.md §7 Resilience](../docs/02-aspnetcore.md#7-resilience) and [docs/06-cloud-native.md §6 Resilience](../docs/06-cloud-native.md#6-resilience-see-ch02-7). - **Output caching** — Prefer the `OutputCache` middleware for response-shaped reuse; data-shaped reuse belongs in the data caching matrix below. See [docs/02-aspnetcore.md §9 Output Caching](../docs/02-aspnetcore.md#9-output-caching). - **`BackgroundService` + `Channel`** — Long-running in-proc work runs as `BackgroundService`; use bounded `Channel` (FullMode = Wait) for backpressure between producer and worker. See [docs/02-aspnetcore.md §12 Background Work](../docs/02-aspnetcore.md#12-background-work). - **Chain of Responsibility = ASP.NET Core middleware** — `app.UseX()` is the canonical CoR pipeline; for exceptions specifically, register multiple `IExceptionHandler`s. - See [docs/02-aspnetcore.md §3 ProblemDetails & Error Handling](../docs/02-aspnetcore.md#3-problemdetails--error-handling). + See [docs/02-aspnetcore.md §3 ProblemDetails & Error Handling](../docs/02-aspnetcore.md#3-problemdetails-error-handling). ## Data - **Repository — only for aggregate roots** — One repository per aggregate root (Evans / Vernon DDD), not per table; `IOrderRepository`, never `IOrderLineRepository`. - See [docs/03-data.md §11 Repository / Specification](../docs/03-data.md#11-repository--specification). + See [docs/03-data.md §11 Repository / Specification](../docs/03-data.md#11-repository-specification). - **Unit of Work = `DbContext`** — EF Core's `ChangeTracker` *is* the Unit of Work; do not wrap it in a custom `IUnitOfWork`. Share the scoped `DbContext` for cross-repo transactional behaviour. - See [docs/03-data.md §6 Transactions & Unit of Work](../docs/03-data.md#6-transactions--unit-of-work). + See [docs/03-data.md §6 Transactions & Unit of Work](../docs/03-data.md#6-transactions-unit-of-work). - **Specification pattern** — Reusable EF query criteria via Ardalis.Specification; only adopt when ≥3 places ask the same question. - See [docs/03-data.md §11 Repository / Specification](../docs/03-data.md#11-repository--specification). + See [docs/03-data.md §11 Repository / Specification](../docs/03-data.md#11-repository-specification). - **CQRS only where asymmetry exists** — Adopt read-model projections only when reads and writes differ in scaling, storage, or consistency; otherwise it is theatre. See [docs/03-data.md §12 CQRS-lite](../docs/03-data.md#12-cqrs-lite). - **Outbox + idempotent handlers** — Never publish from inside `SaveChangesAsync`; write to an outbox in the same transaction and dispatch from a relay. Idempotency keys on every external command. - See [docs/03-data.md §6 Transactions & Unit of Work — Cross-system consistency → Outbox](../docs/03-data.md#cross-system-consistency--outbox). + See [docs/03-data.md §6 Transactions & Unit of Work — Cross-system consistency → Outbox](../docs/03-data.md#cross-system-consistency-outbox). - **Caching matrix (data side)** — Pick the right cache (in-memory, hybrid, distributed) per access pattern; data caches are not the same decision as `OutputCache`. See [docs/03-data.md §14 Caching](../docs/03-data.md#14-caching) and the response-shape counterpart [docs/02-aspnetcore.md §9 Output Caching](../docs/02-aspnetcore.md#9-output-caching). @@ -60,9 +60,9 @@ Decision-bearing prose lives in the chapters; this page only points there. - **Testcontainers** — Real Postgres / SQL Server / Redis in Docker per fixture; faster and more correct than in-memory providers. See [docs/04-testing.md §6 Testcontainers](../docs/04-testing.md#6-testcontainers). - **`TimeProvider` / `FakeTimeProvider` in tests** — Drive time deterministically with `FakeTimeProvider.Advance`; never `Thread.Sleep`. - See [docs/04-testing.md §14 Time](../docs/04-testing.md#time--use-timeprovider-net-8). + See [docs/04-testing.md §14 Time](../docs/04-testing.md#time-use-timeprovider-net-8). - **Snapshot / approval testing** — Use Verify for response-shape and DI-graph snapshots; commit `*.verified.txt`. - See [docs/04-testing.md §7 Snapshot / approval testing with Verify](../docs/04-testing.md#7-snapshot--approval-testing-with-verify). + See [docs/04-testing.md §7 Snapshot / approval testing with Verify](../docs/04-testing.md#7-snapshot-approval-testing-with-verify). - **Architecture tests** — NetArchTest / ArchUnitNET to enforce layer boundaries (e.g. Domain has no EF Core dependency). See [docs/04-testing.md §9 Architecture tests](../docs/04-testing.md#9-architecture-tests). - **Composition root + DI graph test** — Resolve every registered service through the test host to fail fast on missing or cyclical registrations. @@ -71,38 +71,13 @@ Decision-bearing prose lives in the chapters; this page only points there. ## Cloud-native - **Aspire `ServiceDefaults`** — Shared OTel, health-check, resilience, and discovery wire-up; every service references the same `ServiceDefaults` project. - See [docs/06-cloud-native.md §1 .NET Aspire](../docs/06-cloud-native.md#1-net-aspire--what-it-is-what-it-isnt). + See [docs/06-cloud-native.md §1 .NET Aspire](../docs/06-cloud-native.md#1-net-aspire-what-it-is-what-it-isnt). - **Health checks — three endpoints** — `/health/startup`, `/health/live`, `/health/ready` mapped to K8s probes; what `MapDefaultEndpoints` ships is *not* enough. - See [docs/06-cloud-native.md §10 Health checks](../docs/06-cloud-native.md#10-health-checks--three-endpoints-for-k8s-not-what-servicedefaults-gives-you). + See [docs/06-cloud-native.md §10 Health checks](../docs/06-cloud-native.md#10-health-checks-three-endpoints-for-k8s-not-what-servicedefaults-gives-you). - **Graceful shutdown** — Set `HostOptions.ShutdownTimeout` larger than the longest in-flight request; drain on `ApplicationStopping`, not on `StopAsync`. - See [docs/06-cloud-native.md §11 Graceful shutdown](../docs/06-cloud-native.md#11-graceful-shutdown--drain-dont-drop). + See [docs/06-cloud-native.md §11 Graceful shutdown](../docs/06-cloud-native.md#11-graceful-shutdown-drain-dont-drop). - **Telemetry-by-construction** — Every cross-boundary type takes `ILogger`, owns an `ActivitySource`, emits a `Meter`; bake it in, do not retrofit after incidents. - See [docs/06-cloud-native.md §5 Observability](../docs/06-cloud-native.md#5-observability--opentelemetry-one-sdk-three-signals). - ---- - -## Cross-cutting (no single chapter owner) - -These do not map cleanly onto one chapter and stay here. - -### Vertical slice vs Clean Architecture - -- **Vertical slice** ([Jimmy Bogard](https://www.jimmybogard.com/vertical-slice-architecture/)): organise by feature folder; each slice owns its request, handler, validator, DTO. Best where features evolve independently. -- **Clean Architecture** ([Steve Smith template](https://github.com/ardalis/CleanArchitecture)): organise by layer (Domain / Application / Infrastructure / Web). Best for stable, complex domains with cross-cutting policies. -- Hybrid is fine: Clean at the assembly level, vertical-slice within `Application`. Pick a *primary* axis. -- **Don't** mix the two axes at the same level — it produces a folder tree nobody can navigate. - -### Bounded contexts & monorepo strategy - -- One bounded context per deployable; one solution per bounded context inside the monorepo. -- Cross-context calls go over the wire (HTTP / messaging) — never via a shared `Domain` project. -- See [`monorepo.md`](./monorepo.md) for the repository layout rules. - -### Domain events vs integration events - -- Domain events fire inside the aggregate boundary and dispatch in-process after `SaveChanges` (see [docs/03-data.md §6](../docs/03-data.md#6-transactions--unit-of-work)). -- Integration events leave the process via the outbox. -- **Don't** conflate the two — the failure modes differ and one becomes the other only by crossing the outbox. + See [docs/06-cloud-native.md §5 Observability](../docs/06-cloud-native.md#5-observability-opentelemetry-one-sdk-three-signals). --- @@ -111,7 +86,7 @@ These do not map cleanly onto one chapter and stay here. A short pointer list — each pattern's idiomatic .NET-10 form, and which BCL primitive replaces the hand-roll. Use this as a vocabulary cheat-sheet, not as a design checklist. -- **Strategy** — Inject the algorithm via DI; pick at runtime with `IServiceProvider.GetRequiredKeyedService` or `[FromKeyedServices("name")]`. See [docs/01-foundations.md §5](../docs/01-foundations.md#5-di--lifetimes). +- **Strategy** — Inject the algorithm via DI; pick at runtime with `IServiceProvider.GetRequiredKeyedService` or `[FromKeyedServices("name")]`. See [docs/01-foundations.md §5](../docs/01-foundations.md#5-di-lifetimes). - **Decorator** — Wrap an interface with [Scrutor](https://github.com/khellang/Scrutor) `Decorate`; AOT-safe alternative is a source generator. **Don't** use `DispatchProxy` under AOT. - **Adapter** — Hand-write the adapter at the boundary; it documents the seam better than reflection mappers. - **Factory / Abstract Factory** — `IServiceProvider.GetRequiredKeyedService(key)` replaces most factory interfaces; reach for `Func` only when the key is computed. @@ -120,7 +95,7 @@ Use this as a vocabulary cheat-sheet, not as a design checklist. - **Observer** — `Channel` (with backpressure) or MediatR notifications for in-proc fan-out; skip `IObservable` unless you need Rx operators. - **Mediator** — [MediatR](https://github.com/jbogard/MediatR) for in-proc dispatch; [Wolverine](https://wolverinefx.net/) when you also want messaging + outbox in the same primitive. Not a service locator. - **Command** — Same shape as Mediator's request — the *write* side returns an ID or `Result<…>`, never query data back. See CQRS index entry. -- **Chain of Responsibility** — ASP.NET Core middleware *is* CoR; for exceptions, register multiple `IExceptionHandler`s. See [docs/02-aspnetcore.md §3](../docs/02-aspnetcore.md#3-problemdetails--error-handling). +- **Chain of Responsibility** — ASP.NET Core middleware *is* CoR; for exceptions, register multiple `IExceptionHandler`s. See [docs/02-aspnetcore.md §3](../docs/02-aspnetcore.md#3-problemdetails-error-handling). - **Template Method** — `sealed override` on the framework hook + `abstract` on the customisation point. `BackgroundService.ExecuteAsync` is the canonical example. - **Visitor** — C# pattern matching on a sealed hierarchy; mark the base `abstract` and all derivations `sealed` so the compiler warns on missing arms. - **Specification** — See the data-chapter index entry above. @@ -128,7 +103,7 @@ Use this as a vocabulary cheat-sheet, not as a design checklist. - **Memento** — Snapshot via `System.Text.Json` source-gen so the snapshot is a `byte[]`. - **Composite** — The BCL already composes most things you need (`IConfiguration`, `LoggerProvider`, `CompositeFileProvider`). - **Proxy** — Castle DynamicProxy on JIT only; AOT-safe alternative is a Roslyn source generator. Decorator-via-Scrutor covers most "logging around an interface" cases without proxies. -- **Flyweight** — `ArrayPool`, `MemoryPool`, `string.Intern` / `StringPool`. Hand-roll only after a profiler shows allocation pressure. See [docs/05-performance.md §2](../docs/05-performance.md#2-allocations--the-silent-latency-tax). +- **Flyweight** — `ArrayPool`, `MemoryPool`, `string.Intern` / `StringPool`. Hand-roll only after a profiler shows allocation pressure. See [docs/05-performance.md §2](../docs/05-performance.md#2-allocations-the-silent-latency-tax). - **Bridge** — `ILogger` ↔ `ILoggerProvider` is the canonical .NET example; the abstraction and the implementation vary independently. - **Iterator** — `yield return` for sync, `IAsyncEnumerable` + `[EnumeratorCancellation]` for async. **Don't** materialise to `List` unless the caller needs random access. - **State** — [Stateless](https://github.com/dotnet-state-machine/stateless) for non-trivial workflows; an `enum` + `switch` is enough for two states — don't pull in a state-machine library. From 8755209ba56e2c7744d41f89dca3374059c336bb Mon Sep 17 00:00:00 2001 From: mghabin <81494213+MohammadGhabin@users.noreply.github.com> Date: Thu, 30 Apr 2026 18:42:11 +0300 Subject: [PATCH 2/2] docs: restore double-hyphen anchors that GitHub renders from punctuation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub's slug renderer keeps a hyphen for each removed punctuation char, producing '--' where headings contain '—', '&', '/'. Markdownlint MD051 validates intra-file fragments using the GitHub renderer, not the verification script's collapsed-whitespace algorithm. Revert the collapsed anchors so the same-file links in coverage-map resolve. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- checklist.md | 14 ++--- coverage-map.md | 132 ++++++++++++++++++++--------------------- docs/01-foundations.md | 12 ++-- docs/05-performance.md | 12 ++-- docs/decision-trees.md | 12 ++-- patterns/patterns.md | 38 ++++++------ 6 files changed, 110 insertions(+), 110 deletions(-) diff --git a/checklist.md b/checklist.md index e9b5df1..6d1e810 100644 --- a/checklist.md +++ b/checklist.md @@ -90,7 +90,7 @@ See: [`docs/01-foundations.md`](./docs/01-foundations.md), decision tree [11 — - Read `IConfiguration` directly inside business logic. - "Optional" config that silently no-ops in prod. -See: [`docs/01-foundations.md`](./docs/01-foundations.md), [`docs/06-cloud-native.md#4-configuration--configmap--csi-key-vault-not-appsettingsproductionjson`](./docs/06-cloud-native.md#4-configuration-configmap-csi-key-vault-not-appsettingsproductionjson). +See: [`docs/01-foundations.md`](./docs/01-foundations.md), [`docs/06-cloud-native.md#4-configuration--configmap--csi-key-vault-not-appsettingsproductionjson`](./docs/06-cloud-native.md#4-configuration--configmap--csi-key-vault-not-appsettingsproductionjson). ## 6. Logging @@ -107,7 +107,7 @@ See: [`docs/01-foundations.md`](./docs/01-foundations.md), [`docs/06-cloud-nativ - Access tokens, refresh tokens, client secrets, cookies — even truncated — in logs. - `catch { _log.LogError(ex, "boom"); throw; }` — log **or** throw, not both at every layer. -See: [`docs/01-foundations.md`](./docs/01-foundations.md) (primitives) and [`docs/06-cloud-native.md#5-observability--opentelemetry-one-sdk-three-signals`](./docs/06-cloud-native.md#5-observability-opentelemetry-one-sdk-three-signals) (OTLP exporter wiring). +See: [`docs/01-foundations.md`](./docs/01-foundations.md) (primitives) and [`docs/06-cloud-native.md#5-observability--opentelemetry-one-sdk-three-signals`](./docs/06-cloud-native.md#5-observability--opentelemetry-one-sdk-three-signals) (OTLP exporter wiring). ## 7. HTTP (outbound) & resilience @@ -165,7 +165,7 @@ See: [`docs/02-aspnetcore.md`](./docs/02-aspnetcore.md) §§3, 4, 5, 8. - Accept tokens with `ver=1.0` when you expect `2.0` (or vice versa) without explicit handling. - Disable `ValidateIssuer`, `ValidateAudience`, or `ValidateLifetime`. Ever. -See: [`docs/02-aspnetcore.md#10-authnauthz`](./docs/02-aspnetcore.md#10-authnauthz) (owner) and decision tree [12 — auth policy shape](./docs/decision-trees.md#12-auth-policy-shape-delegated-scp-vs-app-only-roles-azp). See also (non-normative, runnable sample): [`mghabin/entra-auth-patterns-dotnet`](https://github.com/mghabin/entra-auth-patterns-dotnet). +See: [`docs/02-aspnetcore.md#10-authnauthz`](./docs/02-aspnetcore.md#10-authnauthz) (owner) and decision tree [12 — auth policy shape](./docs/decision-trees.md#12-auth-policy-shape-delegated-scp-vs-app-only-roles--azp). See also (non-normative, runnable sample): [`mghabin/entra-auth-patterns-dotnet`](https://github.com/mghabin/entra-auth-patterns-dotnet). ## 10. Caching @@ -203,14 +203,14 @@ See: [`docs/02-aspnetcore.md#9-output-caching`](./docs/02-aspnetcore.md#9-output - Lazy loading enabled in web request paths. - Distributed (two-phase / MSDTC) transactions across DB + bus / DB + HTTP / two databases — there is no DTC on Linux .NET, and most managed services don't enlist. -See: [`docs/03-data.md#6-transactions--unit-of-work`](./docs/03-data.md#6-transactions-unit-of-work) (outbox is the single owner here), and decision tree [17 — Cosmos partition key](./docs/decision-trees.md#17-cosmos-db-partition-key) for the modelling rule. +See: [`docs/03-data.md#6-transactions--unit-of-work`](./docs/03-data.md#6-transactions--unit-of-work) (outbox is the single owner here), and decision tree [17 — Cosmos partition key](./docs/decision-trees.md#17-cosmos-db-partition-key) for the modelling rule. ## 12. Background work **Do:** - `BackgroundService` / `IHostedService`; honor the `stoppingToken` in every loop ([learn.microsoft.com/dotnet/core/extensions/workers](https://learn.microsoft.com/dotnet/core/extensions/workers)). -- Graceful shutdown: drain in-flight work within `HostOptions.ShutdownTimeout`; the cluster-side drain contract is owned by [`docs/06-cloud-native.md#11-graceful-shutdown--drain-dont-drop`](./docs/06-cloud-native.md#11-graceful-shutdown-drain-dont-drop). +- Graceful shutdown: drain in-flight work within `HostOptions.ShutdownTimeout`; the cluster-side drain contract is owned by [`docs/06-cloud-native.md#11-graceful-shutdown--drain-dont-drop`](./docs/06-cloud-native.md#11-graceful-shutdown--drain-dont-drop). - Idempotency keys on every externally-visible side effect; safe to replay (same `(key, tenant)` UNIQUE store the outbox/inbox uses — see §11). - Bound concurrency with `Channel`, `Parallel.ForEachAsync`, or a semaphore — not unbounded `Task.Run`. @@ -278,7 +278,7 @@ See: [`docs/05-performance.md`](./docs/05-performance.md), decision trees [9 — - Client secrets in pipelines when FIC works. - Default in-memory Data Protection keys behind a load balancer. -See: [`docs/06-cloud-native.md#10-health-checks--three-endpoints-for-k8s-not-what-servicedefaults-gives-you`](./docs/06-cloud-native.md#10-health-checks-three-endpoints-for-k8s-not-what-servicedefaults-gives-you) (probe contract owner; ch02 §18 only owns the `MapHealthChecks` plumbing), [`docs/06-cloud-native.md#1-net-aspire--what-it-is-what-it-isnt`](./docs/06-cloud-native.md#1-net-aspire-what-it-is-what-it-isnt), decision tree [16 — Aspire scope](./docs/decision-trees.md#16-aspire-scope-apphost-resource-vs-in-service-client-integration). +See: [`docs/06-cloud-native.md#10-health-checks--three-endpoints-for-k8s-not-what-servicedefaults-gives-you`](./docs/06-cloud-native.md#10-health-checks--three-endpoints-for-k8s-not-what-servicedefaults-gives-you) (probe contract owner; ch02 §18 only owns the `MapHealthChecks` plumbing), [`docs/06-cloud-native.md#1-net-aspire--what-it-is-what-it-isnt`](./docs/06-cloud-native.md#1-net-aspire--what-it-is-what-it-isnt), decision tree [16 — Aspire scope](./docs/decision-trees.md#16-aspire-scope-apphost-resource-vs-in-service-client-integration). ## 16. Security @@ -296,7 +296,7 @@ See: [`docs/06-cloud-native.md#10-health-checks--three-endpoints-for-k8s-not-wha - Write tokens or keys to local disk; use `DataProtection`, Key Vault, or memory only. - Disable certificate validation ("just for staging"). -See: [`docs/02-aspnetcore.md#14-security`](./docs/02-aspnetcore.md#14-security), [`docs/06-cloud-native.md#8-secrets--identity--workload-identity-only`](./docs/06-cloud-native.md#8-secrets-identity-workload-identity-only). +See: [`docs/02-aspnetcore.md#14-security`](./docs/02-aspnetcore.md#14-security), [`docs/06-cloud-native.md#8-secrets--identity--workload-identity-only`](./docs/06-cloud-native.md#8-secrets--identity--workload-identity-only). ## 17. Dependencies diff --git a/coverage-map.md b/coverage-map.md index 616ccf3..952f377 100644 --- a/coverage-map.md +++ b/coverage-map.md @@ -42,18 +42,18 @@ Source: [`docs/01-foundations.md`](./docs/01-foundations.md). ### References (does not re-decide) -- DI usage inside endpoints and filters → [Chapter 02 — aspnetcore](#chapter-02-aspnetcore). -- EF Core `DbContext` lifetime and pooling → [Chapter 03 — data](#chapter-03-data). -- Hosted-service testing patterns → [Chapter 04 — testing](#chapter-04-testing). -- NativeAOT, GC tuning specifics, container env-vars → [Chapter 05 — performance](#chapter-05-performance). -- OTLP exporters and `ServiceDefaults` wiring → [Chapter 06 — cloud-native](#chapter-06-cloud-native). -- Blazor / MAUI client lifetimes and render modes → [Chapter 07 — client](#chapter-07-client). +- DI usage inside endpoints and filters → [Chapter 02 — aspnetcore](#chapter-02--aspnetcore). +- EF Core `DbContext` lifetime and pooling → [Chapter 03 — data](#chapter-03--data). +- Hosted-service testing patterns → [Chapter 04 — testing](#chapter-04--testing). +- NativeAOT, GC tuning specifics, container env-vars → [Chapter 05 — performance](#chapter-05--performance). +- OTLP exporters and `ServiceDefaults` wiring → [Chapter 06 — cloud-native](#chapter-06--cloud-native). +- Blazor / MAUI client lifetimes and render modes → [Chapter 07 — client](#chapter-07--client). ### Cross-links -- Logging primitives owned here ↔ telemetry export owned in [06](#chapter-06-cloud-native) and meter design in [05](#chapter-05-performance). -- DI lifetimes owned here ↔ endpoint DI surface in [02](#chapter-02-aspnetcore) ↔ `DbContext` scoping in [03](#chapter-03-data). -- Options pattern owned here ↔ secrets/config in cloud in [06](#chapter-06-cloud-native). +- Logging primitives owned here ↔ telemetry export owned in [06](#chapter-06--cloud-native) and meter design in [05](#chapter-05--performance). +- DI lifetimes owned here ↔ endpoint DI surface in [02](#chapter-02--aspnetcore) ↔ `DbContext` scoping in [03](#chapter-03--data). +- Options pattern owned here ↔ secrets/config in cloud in [06](#chapter-06--cloud-native). --- @@ -73,27 +73,27 @@ Source: [`docs/02-aspnetcore.md`](./docs/02-aspnetcore.md). - **HTTP resilience for outbound calls** (§7): `IHttpClientFactory` + `AddStandardResilienceHandler` as the single owner of retry / timeout / circuit-breaker / hedging defaults. Chapter 06 references this section, does not re-decide. - **`HttpClient` factory and typed clients** (§11): `AddHttpClient`, named vs typed, `SocketsHttpHandler` defaults, `PooledConnectionLifetime`. - **OutputCache** (§9): HTTP-response caching middleware (`AddOutputCache`, policies, tag invalidation). Chapter 03's caching matrix links here for the OutputCache row. -- **Background work in the request host** (§12): `IHostedService` / `BackgroundService` patterns, `Channel` pipelines, graceful drain of in-flight work — composition lifetime owned here; the cluster-side drain contract is owned by [06 §11](#chapter-06-cloud-native). +- **Background work in the request host** (§12): `IHostedService` / `BackgroundService` patterns, `Channel` pipelines, graceful drain of in-flight work — composition lifetime owned here; the cluster-side drain contract is owned by [06 §11](#chapter-06--cloud-native). - **Security middleware** (§14): HTTPS redirection, HSTS, security headers, antiforgery, request-size limits, forwarded headers (paired with §15). - **gRPC surface** (§16): `MapGrpcService`, gRPC-Web, code-first vs proto-first, interceptors, deadlines. - **SignalR surface** (§17): hub authorization, backplane choice, scaling rules, sticky sessions. -- **Health-check endpoint mapping** (§18): the in-process `MapHealthChecks` helpers and how the request host exposes them — but the **probe contract** (endpoint names, semantics, what a check may do) is owned by [06 §10](#chapter-06-cloud-native). +- **Health-check endpoint mapping** (§18): the in-process `MapHealthChecks` helpers and how the request host exposes them — but the **probe contract** (endpoint names, semantics, what a check may do) is owned by [06 §10](#chapter-06--cloud-native). - Rate limiting middleware (§8), request size limits, and Kestrel surface defaults relevant to APIs. ### References (does not re-decide) -- Logging primitives and source-generated loggers → [Chapter 01 — foundations](#chapter-01-foundations). +- Logging primitives and source-generated loggers → [Chapter 01 — foundations](#chapter-01--foundations). - Validation libraries and model-binding edge cases → [`docs/01-foundations.md`](./docs/01-foundations.md) and `validation.md` referenced from chapter 02. -- EF Core `DbContext` registration and async query patterns → [Chapter 03 — data](#chapter-03-data). -- `WebApplicationFactory` integration tests → [Chapter 04 — testing](#chapter-04-testing). -- OTel meters and application metrics → [Chapter 05 — performance](#chapter-05-performance). -- OTLP exporters, `ServiceDefaults`, K8s probes → [Chapter 06 — cloud-native](#chapter-06-cloud-native). +- EF Core `DbContext` registration and async query patterns → [Chapter 03 — data](#chapter-03--data). +- `WebApplicationFactory` integration tests → [Chapter 04 — testing](#chapter-04--testing). +- OTel meters and application metrics → [Chapter 05 — performance](#chapter-05--performance). +- OTLP exporters, `ServiceDefaults`, K8s probes → [Chapter 06 — cloud-native](#chapter-06--cloud-native). ### Cross-links -- ProblemDetails owned here ↔ EF Core exception translation in [03](#chapter-03-data). -- JWT bearer + Entra owned here ↔ Blazor auth-state serialization in [07](#chapter-07-client). -- OTel HTTP wiring owned here ↔ OTel application metrics in [05](#chapter-05-performance) ↔ OTLP exporters in [06](#chapter-06-cloud-native). +- ProblemDetails owned here ↔ EF Core exception translation in [03](#chapter-03--data). +- JWT bearer + Entra owned here ↔ Blazor auth-state serialization in [07](#chapter-07--client). +- OTel HTTP wiring owned here ↔ OTel application metrics in [05](#chapter-05--performance) ↔ OTLP exporters in [06](#chapter-06--cloud-native). --- @@ -114,17 +114,17 @@ Source: [`docs/03-data.md`](./docs/03-data.md). ### References (does not re-decide) -- DI lifetimes underlying `DbContext` registration → [Chapter 01 — foundations](#chapter-01-foundations). -- Returning `ProblemDetails` for data-layer failures → [Chapter 02 — aspnetcore](#chapter-02-aspnetcore). -- Respawn-based test data reset and Testcontainers for SQL/Cosmos → [Chapter 04 — testing](#chapter-04-testing). -- Span/Memory/Pipelines for high-throughput data paths → [Chapter 05 — performance](#chapter-05-performance). -- Outbox dispatcher hosted in the cluster, message broker integrations → [Chapter 06 — cloud-native](#chapter-06-cloud-native). +- DI lifetimes underlying `DbContext` registration → [Chapter 01 — foundations](#chapter-01--foundations). +- Returning `ProblemDetails` for data-layer failures → [Chapter 02 — aspnetcore](#chapter-02--aspnetcore). +- Respawn-based test data reset and Testcontainers for SQL/Cosmos → [Chapter 04 — testing](#chapter-04--testing). +- Span/Memory/Pipelines for high-throughput data paths → [Chapter 05 — performance](#chapter-05--performance). +- Outbox dispatcher hosted in the cluster, message broker integrations → [Chapter 06 — cloud-native](#chapter-06--cloud-native). ### Cross-links -- Outbox owned here ↔ broker/dispatcher topology in [06](#chapter-06-cloud-native). -- Caching matrix owned here ↔ Data Protection key ring (separate concern) in [06](#chapter-06-cloud-native). -- Migration bundles owned here ↔ K8s init-container / job invocation in [06](#chapter-06-cloud-native). +- Outbox owned here ↔ broker/dispatcher topology in [06](#chapter-06--cloud-native). +- Caching matrix owned here ↔ Data Protection key ring (separate concern) in [06](#chapter-06--cloud-native). +- Migration bundles owned here ↔ K8s init-container / job invocation in [06](#chapter-06--cloud-native). --- @@ -145,18 +145,18 @@ Source: [`docs/04-testing.md`](./docs/04-testing.md). ### References (does not re-decide) -- Code under test conventions (NRT, async, DI lifetimes) → [Chapter 01 — foundations](#chapter-01-foundations). -- Endpoint construction and ProblemDetails contracts being asserted → [Chapter 02 — aspnetcore](#chapter-02-aspnetcore). -- EF Core fixtures, migrations, and Cosmos provider behavior → [Chapter 03 — data](#chapter-03-data). -- BenchmarkDotNet and perf assertions → [Chapter 05 — performance](#chapter-05-performance). -- Aspire AppHost wiring under test → [Chapter 06 — cloud-native](#chapter-06-cloud-native). -- Blazor `bUnit` and MAUI device-test specifics → [Chapter 07 — client](#chapter-07-client). +- Code under test conventions (NRT, async, DI lifetimes) → [Chapter 01 — foundations](#chapter-01--foundations). +- Endpoint construction and ProblemDetails contracts being asserted → [Chapter 02 — aspnetcore](#chapter-02--aspnetcore). +- EF Core fixtures, migrations, and Cosmos provider behavior → [Chapter 03 — data](#chapter-03--data). +- BenchmarkDotNet and perf assertions → [Chapter 05 — performance](#chapter-05--performance). +- Aspire AppHost wiring under test → [Chapter 06 — cloud-native](#chapter-06--cloud-native). +- Blazor `bUnit` and MAUI device-test specifics → [Chapter 07 — client](#chapter-07--client). ### Cross-links -- `DistributedApplicationTestingBuilder` owned here ↔ Aspire AppHost in [06](#chapter-06-cloud-native). -- `WebApplicationFactory` owned here ↔ JWT bearer + ProblemDetails in [02](#chapter-02-aspnetcore). -- Respawn owned here ↔ EF migrations in [03](#chapter-03-data). +- `DistributedApplicationTestingBuilder` owned here ↔ Aspire AppHost in [06](#chapter-06--cloud-native). +- `WebApplicationFactory` owned here ↔ JWT bearer + ProblemDetails in [02](#chapter-02--aspnetcore). +- Respawn owned here ↔ EF migrations in [03](#chapter-03--data). --- @@ -176,17 +176,17 @@ Source: [`docs/05-performance.md`](./docs/05-performance.md). ### References (does not re-decide) -- `async`/`await`, `ValueTask`, source generators as the language baseline → [Chapter 01 — foundations](#chapter-01-foundations). -- HTTP-pipeline OTel instrumentation that produces transport metrics → [Chapter 02 — aspnetcore](#chapter-02-aspnetcore). -- EF Core query-shape choices that dominate allocations → [Chapter 03 — data](#chapter-03-data). -- Microbenchmark fixtures vs integration perf tests → [Chapter 04 — testing](#chapter-04-testing). -- OTLP exporters, collector, and K8s CPU/QoS that the metrics are read from → [Chapter 06 — cloud-native](#chapter-06-cloud-native). +- `async`/`await`, `ValueTask`, source generators as the language baseline → [Chapter 01 — foundations](#chapter-01--foundations). +- HTTP-pipeline OTel instrumentation that produces transport metrics → [Chapter 02 — aspnetcore](#chapter-02--aspnetcore). +- EF Core query-shape choices that dominate allocations → [Chapter 03 — data](#chapter-03--data). +- Microbenchmark fixtures vs integration perf tests → [Chapter 04 — testing](#chapter-04--testing). +- OTLP exporters, collector, and K8s CPU/QoS that the metrics are read from → [Chapter 06 — cloud-native](#chapter-06--cloud-native). ### Cross-links -- App metrics meters owned here ↔ OTLP exporters in [06](#chapter-06-cloud-native). -- Container env-vars owned here ↔ Dockerfile baseline in [06](#chapter-06-cloud-native). -- NativeAOT owned here ↔ source generators in [01](#chapter-01-foundations). +- App metrics meters owned here ↔ OTLP exporters in [06](#chapter-06--cloud-native). +- Container env-vars owned here ↔ Dockerfile baseline in [06](#chapter-06--cloud-native). +- NativeAOT owned here ↔ source generators in [01](#chapter-01--foundations). --- @@ -199,8 +199,8 @@ Source: [`docs/06-cloud-native.md`](./docs/06-cloud-native.md). - .NET Aspire AppHost composition and client integrations — **Aspire 9.x** for `net8.0` / `net9.0` services, **Aspire 13** for `net10.0` services (`Aspire.Hosting.*`, `Aspire.*`). - `ServiceDefaults` shared project (§1): OTel wiring, default health checks, resilience handlers, service discovery defaults. - **Configuration in cluster** (§4): ConfigMap + CSI Key Vault driver as the configuration substrate; `appsettings.Production.json` rejected. Foundations §6 owns the in-process options pattern; this chapter owns where the bytes come from in the cluster. -- **Resilience pipeline defaults at the platform layer** (§6): Polly v8 standard-pipeline composition for non-HTTP resources (queues, blobs, repositories) and the rationale for keeping HTTP resilience in [02 §7](#chapter-02-aspnetcore). HTTP retry verb policy is owned by ch02; this chapter does not re-decide it. -- **Health-check probe contract** (§10): `/health/live`, `/health/ready`, `/health/startup` endpoint names, semantics, what each probe may do, and the K8s probe wiring. Aspire `ServiceDefaults` ships **only** `/health` + `/alive` and **only in Development** — production code must explicitly `MapHealthChecks` for `/health/live`, `/health/ready`, `/health/startup` (see [ch06 §10](#chapter-06-cloud-native) and the [aspire.dev ServiceDefaults reference](https://aspire.dev/fundamentals/service-defaults/)). Chapter 02 §18 only owns the in-process `MapHealthChecks` plumbing and links here for the contract. +- **Resilience pipeline defaults at the platform layer** (§6): Polly v8 standard-pipeline composition for non-HTTP resources (queues, blobs, repositories) and the rationale for keeping HTTP resilience in [02 §7](#chapter-02--aspnetcore). HTTP retry verb policy is owned by ch02; this chapter does not re-decide it. +- **Health-check probe contract** (§10): `/health/live`, `/health/ready`, `/health/startup` endpoint names, semantics, what each probe may do, and the K8s probe wiring. Aspire `ServiceDefaults` ships **only** `/health` + `/alive` and **only in Development** — production code must explicitly `MapHealthChecks` for `/health/live`, `/health/ready`, `/health/startup` (see [ch06 §10](#chapter-06--cloud-native) and the [aspire.dev ServiceDefaults reference](https://aspire.dev/fundamentals/service-defaults/)). Chapter 02 §18 only owns the in-process `MapHealthChecks` plumbing and links here for the contract. - Kubernetes runtime contract: liveness / readiness / startup probes, QoS class, the CPU-limits-considered-harmful stance, requests sizing. - Service discovery via `Microsoft.Extensions.ServiceDiscovery` and `HttpClient` integration (§7). - **Networking** (§13): HTTP/2 + HTTP/3 enablement, forwarded headers in cluster, mTLS via service mesh, TLS termination boundary. @@ -208,25 +208,25 @@ Source: [`docs/06-cloud-native.md`](./docs/06-cloud-native.md). - **Secrets & identity** (§8): Workload Identity + Federated Identity Credentials only — no client secrets, no pod-identity v1, no service-principal passwords on disk. - ASP.NET Core Data Protection key-ring storage (Azure Blob + Key Vault, or equivalent) for multi-replica deployments (§9). - **Graceful shutdown** (§11): `IHostApplicationLifetime`, `preStop` hook, drain order, K8s `terminationGracePeriodSeconds`. Chapter 02's background-work section composes against this contract. -- Outbox **dispatcher** topology (hosted service / sidecar / worker pool) — the pattern itself is owned by [03](#chapter-03-data). -- **CI/CD** (§12): reproducible image build, Sigstore/cosign signing, SBOM, scanning gate. Source-build hygiene is owned by [01 §11](#chapter-01-foundations); this chapter owns the cluster-deployment pipeline. +- Outbox **dispatcher** topology (hosted service / sidecar / worker pool) — the pattern itself is owned by [03](#chapter-03--data). +- **CI/CD** (§12): reproducible image build, Sigstore/cosign signing, SBOM, scanning gate. Source-build hygiene is owned by [01 §11](#chapter-01--foundations); this chapter owns the cluster-deployment pipeline. - **Multi-tenancy at runtime** (§14): tenant-scoped DI, per-tenant configuration / connection-string resolution, isolation expectations at the cluster boundary. - **Cost & efficiency** (§15): right-sizing requests, scale-to-zero suitability, image size baseline, the cluster-level levers a service team owns. -- Dockerfile baseline for .NET 10 images, including the runtime env-vars selected in [05](#chapter-05-performance). +- Dockerfile baseline for .NET 10 images, including the runtime env-vars selected in [05](#chapter-05--performance). ### References (does not re-decide) -- Logging primitives feeding the OTLP log exporter → [Chapter 01 — foundations](#chapter-01-foundations). -- Endpoint surface and OTel HTTP instrumentation → [Chapter 02 — aspnetcore](#chapter-02-aspnetcore). -- EF migration bundles invoked as init-containers / Jobs → [Chapter 03 — data](#chapter-03-data). -- `DistributedApplicationTestingBuilder` and integration-test wiring → [Chapter 04 — testing](#chapter-04-testing). -- GC and runtime env-vars copied into the Dockerfile → [Chapter 05 — performance](#chapter-05-performance). +- Logging primitives feeding the OTLP log exporter → [Chapter 01 — foundations](#chapter-01--foundations). +- Endpoint surface and OTel HTTP instrumentation → [Chapter 02 — aspnetcore](#chapter-02--aspnetcore). +- EF migration bundles invoked as init-containers / Jobs → [Chapter 03 — data](#chapter-03--data). +- `DistributedApplicationTestingBuilder` and integration-test wiring → [Chapter 04 — testing](#chapter-04--testing). +- GC and runtime env-vars copied into the Dockerfile → [Chapter 05 — performance](#chapter-05--performance). ### Cross-links -- Aspire `ServiceDefaults` owned here ↔ OTel HTTP wiring in [02](#chapter-02-aspnetcore) ↔ app metrics in [05](#chapter-05-performance). -- K8s probes owned here ↔ `IHostApplicationLifetime` / health checks contract in [01](#chapter-01-foundations). -- Data Protection key ring owned here ↔ cookie/antiforgery surfaces in [02](#chapter-02-aspnetcore) and [07](#chapter-07-client). +- Aspire `ServiceDefaults` owned here ↔ OTel HTTP wiring in [02](#chapter-02--aspnetcore) ↔ app metrics in [05](#chapter-05--performance). +- K8s probes owned here ↔ `IHostApplicationLifetime` / health checks contract in [01](#chapter-01--foundations). +- Data Protection key ring owned here ↔ cookie/antiforgery surfaces in [02](#chapter-02--aspnetcore) and [07](#chapter-07--client). --- @@ -246,16 +246,16 @@ Source: [`docs/07-client.md`](./docs/07-client.md). ### References (does not re-decide) -- DI lifetimes, NRT, async fundamentals as language baseline → [Chapter 01 — foundations](#chapter-01-foundations). -- JWT bearer + Entra token shapes the client consumes → [Chapter 02 — aspnetcore](#chapter-02-aspnetcore). -- `bUnit` / device-test plumbing → [Chapter 04 — testing](#chapter-04-testing). -- Data Protection key ring backing Blazor auth cookies in multi-replica → [Chapter 06 — cloud-native](#chapter-06-cloud-native). +- DI lifetimes, NRT, async fundamentals as language baseline → [Chapter 01 — foundations](#chapter-01--foundations). +- JWT bearer + Entra token shapes the client consumes → [Chapter 02 — aspnetcore](#chapter-02--aspnetcore). +- `bUnit` / device-test plumbing → [Chapter 04 — testing](#chapter-04--testing). +- Data Protection key ring backing Blazor auth cookies in multi-replica → [Chapter 06 — cloud-native](#chapter-06--cloud-native). ### Cross-links -- Auth-state serialization owned here ↔ JWT/Entra validation in [02](#chapter-02-aspnetcore). -- Antiforgery on `EditForm` owned here ↔ antiforgery middleware in [02](#chapter-02-aspnetcore). -- Render-mode decision table owned here ↔ Data Protection key ring in [06](#chapter-06-cloud-native). +- Auth-state serialization owned here ↔ JWT/Entra validation in [02](#chapter-02--aspnetcore). +- Antiforgery on `EditForm` owned here ↔ antiforgery middleware in [02](#chapter-02--aspnetcore). +- Render-mode decision table owned here ↔ Data Protection key ring in [06](#chapter-06--cloud-native). --- @@ -272,13 +272,13 @@ Sources: [`patterns/anti-patterns.md`](./patterns/anti-patterns.md), [`patterns/ ### References (does not re-decide) -- Subsystem-specific anti-patterns stay in their owning chapter (e.g., EF tracking misuse stays in [03](#chapter-03-data); captured-async pitfalls stay in [01](#chapter-01-foundations)). +- Subsystem-specific anti-patterns stay in their owning chapter (e.g., EF tracking misuse stays in [03](#chapter-03--data); captured-async pitfalls stay in [01](#chapter-01--foundations)). - Per-claim citations for chapter-level rules stay inline in the owning chapter, not in `references.md`. ### Cross-links - `patterns/patterns.md` ↔ chapters 01–07 (each named pattern points back to its owning chapter for the binding rule). -- `patterns/monorepo.md` ↔ [04 — testing](#chapter-04-testing) for fan-out test selection and [06 — cloud-native](#chapter-06-cloud-native) for image-build topology. +- `patterns/monorepo.md` ↔ [04 — testing](#chapter-04--testing) for fan-out test selection and [06 — cloud-native](#chapter-06--cloud-native) for image-build topology. --- diff --git a/docs/01-foundations.md b/docs/01-foundations.md index 9ed9d55..d316c42 100644 --- a/docs/01-foundations.md +++ b/docs/01-foundations.md @@ -3,7 +3,7 @@ Opinionated, cross-cutting defaults for a .NET 10 codebase. Everything here is do-this-by-default; deviations should be justified in code review. ASP.NET Core surface lives in [chapter 02](./02-aspnetcore.md), data in [chapter 03](./03-data.md), testing in [chapter 04](./04-testing.md), perf in [chapter 05](./05-performance.md), cloud-native in [chapter 06](./06-cloud-native.md), client in [chapter 07](./07-client.md). -Ownership of each concept (and what defers where) is recorded in [`coverage-map.md`](../coverage-map.md#chapter-01-foundations). +Ownership of each concept (and what defers where) is recorded in [`coverage-map.md`](../coverage-map.md#chapter-01--foundations). > Conventions: **Do** = required default. **Don't** = reject in review unless a > written exception exists. **Prefer** = strong default; use the alternative @@ -464,7 +464,7 @@ never re-resolves). Add resilience via `Microsoft.Extensions.Http.Resilience` (Polly v8 under the hood): `.AddStandardResilienceHandler()` gets you retry+circuit-breaker+timeout defaults that are sensible. -The cluster-level resilience story (Aspire `ServiceDefaults` wiring of the same handler across every outbound `HttpClient`) is owned by [chapter 06 §6 Resilience](./06-cloud-native.md#6-resilience-see-ch02-7); this section owns the per-client defaults, that section owns the platform default. +The cluster-level resilience story (Aspire `ServiceDefaults` wiring of the same handler across every outbound `HttpClient`) is owned by [chapter 06 §6 Resilience](./06-cloud-native.md#6-resilience--polly-v8-standard-pipelines-not-hand-rolled-retries); this section owns the per-client defaults, that section owns the platform default. **Sources:** @@ -514,7 +514,7 @@ environment variables → command-line. Don't fight the precedence; document it. process and shell history. Use `dotnet user-secrets` for local dev. - In every other environment, use Key Vault (or your cloud equivalent) and reference via the configuration provider; the provider handles rotation. - Cluster-side secrets topology (Workload Identity, CSI Key Vault driver, ConfigMap layering) is owned by [chapter 06 §4 Configuration](./06-cloud-native.md#4-configuration-configmap-csi-key-vault-not-appsettingsproductionjson) and [chapter 06 §8 Secrets & identity](./06-cloud-native.md#8-secrets-identity-workload-identity-only); this section owns only the in-process binding/validation rules. + Cluster-side secrets topology (Workload Identity, CSI Key Vault driver, ConfigMap layering) is owned by [chapter 06 §4 Configuration](./06-cloud-native.md#4-configuration--configmap--csi-key-vault-not-appsettingsproductionjson) and [chapter 06 §8 Secrets & identity](./06-cloud-native.md#8-secrets--identity--workload-identity-only); this section owns only the in-process binding/validation rules. - `ILogger` redaction: ensure secrets are never tokens in structured log templates; wrap in a custom type that overrides `ToString()`. @@ -573,7 +573,7 @@ exceptional — return them as values (model-state errors, `ProblemDetails`, `Result`). Authorization failures, missing-but-expected resources, optimistic concurrency, downstream timeouts — all *expected* on a healthy system; design them as flow, not throws. -The HTTP error envelope (`ProblemDetails` per RFC 9457, `AddProblemDetails`, exception-handler middleware) is owned by [chapter 02 §3 ProblemDetails & Error Handling](./02-aspnetcore.md#3-problemdetails-error-handling); this section owns only the in-process exception discipline. +The HTTP error envelope (`ProblemDetails` per RFC 9457, `AddProblemDetails`, exception-handler middleware) is owned by [chapter 02 §3 ProblemDetails & Error Handling](./02-aspnetcore.md#3-problemdetails--error-handling); this section owns only the in-process exception discipline. **Don't catch `Exception`.** Two exceptions: (1) a top-level boundary that logs and converts to a transport error (ASP.NET exception handler middleware, @@ -698,7 +698,7 @@ default; fall back to reflection only when shape isn't statically knowable. - **Minimal API `RequestDelegate` generator** (on by default in ASP.NET Core 7+). Removes per-request reflection; you get it for free as long as your endpoint signatures are static. Don't dynamically build endpoints if you - can express them statically — see [chapter 02 §1 Minimal APIs vs Controllers](./02-aspnetcore.md#1-minimal-apis-vs-controllers) for the endpoint-shape rules and [chapter 05 §6 Reflection alternatives](./05-performance.md#6-reflection-alternatives-source-generators-win) for the perf rationale that puts generators ahead of reflection by default. + can express them statically — see [chapter 02 §1 Minimal APIs vs Controllers](./02-aspnetcore.md#1-minimal-apis-vs-controllers) for the endpoint-shape rules and [chapter 05 §6 Reflection alternatives](./05-performance.md#6-reflection-alternatives--source-generators-win) for the perf rationale that puts generators ahead of reflection by default. `StringSyntaxAttribute` is *not* a source generator — it's a metadata attribute consumed by analyzers/IDEs; see §3. @@ -798,7 +798,7 @@ The runner choice, opt-in, and CI integration rules are owned by [chapter 04 §2 The defaults are good. Change them only with measurement, and pin the overrides in source so they ride with the binary instead of living in a deployment script. -NativeAOT specifics, `DOTNET_*` Dockerfile env-vars, and `GCHeapCount` tuning are owned by [chapter 05 §8 JIT & AOT](./05-performance.md#8-jit-aot), [chapter 05 §9 GC](./05-performance.md#9-gc), and [chapter 05 §11 Containers](./05-performance.md#11-containers-what-to-set); the container baseline that consumes them is owned by [chapter 06 §2 Containerization](./06-cloud-native.md#2-containerization-no-dockerfile-if-you-can-avoid-it) and [chapter 06 §3 Kubernetes / AKS](./06-cloud-native.md#3-kubernetes-aks-probes-limits-gc-shutdown). +NativeAOT specifics, `DOTNET_*` Dockerfile env-vars, and `GCHeapCount` tuning are owned by [chapter 05 §8 JIT & AOT](./05-performance.md#8-jit--aot), [chapter 05 §9 GC](./05-performance.md#9-gc), and [chapter 05 §11 Containers](./05-performance.md#11-containers--what-to-set); the container baseline that consumes them is owned by [chapter 06 §2 Containerization](./06-cloud-native.md#2-containerization--no-dockerfile-if-you-can-avoid-it) and [chapter 06 §3 Kubernetes / AKS](./06-cloud-native.md#3-kubernetes--aks--probes-limits-gc-shutdown). This section owns only the in-process, in-csproj defaults. **Server GC for server workloads.** ASP.NET Core and most service hosts diff --git a/docs/05-performance.md b/docs/05-performance.md index c819eb8..d3d3977 100644 --- a/docs/05-performance.md +++ b/docs/05-performance.md @@ -9,7 +9,7 @@ Dense, opinionated, sourced. Targets **.NET 10 (LTS, Nov 2025)**. Assumes ASP.NE - **Default benchmarking framework: BenchmarkDotNet.** No fallback. Everything else is BenchmarkDotNet with bugs — `Stopwatch` loops, custom harnesses, `xUnit` "perf tests" all skip warmup, JIT tier promotion, and statistical analysis. See §1 and §15. - **Default live-process profiler: `dotnet-trace`.** Cross-platform, sampled, ships in the SDK toolset, feeds PerfView and Speedscope. **Fallback: PerfView on Windows** when you need full ETW depth; **JetBrains dotTrace/dotMemory** when a GUI is non-negotiable for the audience. See §1. - **Default container GC mode: Server GC + concurrent + DATAS.** ASP.NET Core's host turns Server GC on; .NET 9+ makes DATAS the default Server-GC sub-mode. **Fallback: Workstation GC** — and the runtime forces it for you when the container sees `< 1` logical CPU, regardless of config. See §9 and §11. -- **Default container base image: `mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled`.** Distroless, non-root, ~100 MB. **Fallback: `azurelinux`** when you need a Microsoft-supported distro with a shell for debugging. See §11 and [Chapter 06 §2 — Containerization](./06-cloud-native.md#2-containerization-no-dockerfile-if-you-can-avoid-it). +- **Default container base image: `mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled`.** Distroless, non-root, ~100 MB. **Fallback: `azurelinux`** when you need a Microsoft-supported distro with a shell for debugging. See §11 and [Chapter 06 §2 — Containerization](./06-cloud-native.md#2-containerization--no-dockerfile-if-you-can-avoid-it). - **Runtime knobs that belong here, not scattered:** GC mode, DATAS, tiered compilation, dynamic PGO, ReadyToRun, NativeAOT, container env-vars (`DOTNET_*`). The *foundational* runtime-config surface — how to set them per-process via `runtimeconfig.json` / `` / env-vars — is owned by [Chapter 01 §12 — Runtime configuration](./01-foundations.md#12-runtime-configuration); this chapter decides *which values* to ship. --- @@ -285,7 +285,7 @@ Reflection is allocation, slow startup, and AOT-hostile. Most reasons to use it - Latency modes: https://learn.microsoft.com/dotnet/standard/garbage-collection/latency - GC configuration knobs (`Server`, `DynamicAdaptationMode`, `HeapHardLimit*`, `RetainVM`): https://learn.microsoft.com/dotnet/core/runtime-config/garbage-collector - Process-level runtime config wiring for these knobs → [Chapter 01 §12 — Runtime configuration](./01-foundations.md#12-runtime-configuration). -- Pod-level memory requests/limits and QoS class that the GC reads → [Chapter 06 §3 — Kubernetes / AKS](./06-cloud-native.md#3-kubernetes-aks-probes-limits-gc-shutdown). +- Pod-level memory requests/limits and QoS class that the GC reads → [Chapter 06 §3 — Kubernetes / AKS](./06-cloud-native.md#3-kubernetes--aks--probes-limits-gc-shutdown). --- @@ -321,7 +321,7 @@ Reflection is allocation, slow startup, and AOT-hostile. Most reasons to use it There is **no universal "perf-tuned" env-var set**. The runtime defaults are good. Each tunable below moves a specific axis (startup vs steady-state vs memory) and can regress the others. Decide per service, then bake into the image. (**Env-var prefix:** `DOTNET_` is the standardized prefix since .NET 6; the old `COMPlus_` names still work but new code should use `DOTNET_`.) -**Default base image, decided here:** **`mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled`** — distroless, non-root (UID 1654), ~100 MB, no shell, smaller CVE surface. **Fallback: `azurelinux`** when you need a Microsoft-supported distro that includes a shell for in-container debugging; **`alpine`** only when image size dominates and you've audited native interop for musl. The Dockerfile baseline that wires this image into a multi-stage SDK build belongs to [Chapter 06 §2 — Containerization](./06-cloud-native.md#2-containerization-no-dockerfile-if-you-can-avoid-it); this chapter only picks the runtime knobs that go into the `ENV` lines. +**Default base image, decided here:** **`mcr.microsoft.com/dotnet/aspnet:10.0-noble-chiseled`** — distroless, non-root (UID 1654), ~100 MB, no shell, smaller CVE surface. **Fallback: `azurelinux`** when you need a Microsoft-supported distro that includes a shell for in-container debugging; **`alpine`** only when image size dominates and you've audited native interop for musl. The Dockerfile baseline that wires this image into a multi-stage SDK build belongs to [Chapter 06 §2 — Containerization](./06-cloud-native.md#2-containerization--no-dockerfile-if-you-can-avoid-it); this chapter only picks the runtime knobs that go into the `ENV` lines. **Minimal Dockerfile (no tuning, just sane base):** @@ -376,8 +376,8 @@ ENV DOTNET_GCHeapHardLimitPercent=75 - SDK container publish: https://learn.microsoft.com/dotnet/core/docker/publish-as-container - Adam Sitnik on container env-var benchmarking — https://adamsitnik.com/ - How to *set* these env-vars per host (`runtimeconfig.json`, ``, `ENV` precedence) → [Chapter 01 §12 — Runtime configuration](./01-foundations.md#12-runtime-configuration). -- Dockerfile / chiseled-image baseline that consumes these env-vars → [Chapter 06 §2 — Containerization](./06-cloud-native.md#2-containerization-no-dockerfile-if-you-can-avoid-it). -- Pod sizing, QoS class, and the `cpu` requests/limits that the GC reads → [Chapter 06 §3 — Kubernetes / AKS](./06-cloud-native.md#3-kubernetes-aks-probes-limits-gc-shutdown). +- Dockerfile / chiseled-image baseline that consumes these env-vars → [Chapter 06 §2 — Containerization](./06-cloud-native.md#2-containerization--no-dockerfile-if-you-can-avoid-it). +- Pod sizing, QoS class, and the `cpu` requests/limits that the GC reads → [Chapter 06 §3 — Kubernetes / AKS](./06-cloud-native.md#3-kubernetes--aks--probes-limits-gc-shutdown). --- @@ -408,7 +408,7 @@ ENV DOTNET_GCHeapHardLimitPercent=75 **Sources:** - `LoggerMessage` source generator: https://learn.microsoft.com/dotnet/core/extensions/logger-message-generator - High-performance logging: https://learn.microsoft.com/dotnet/core/extensions/high-performance-logging -- Logging primitives, `ILogger`, and source-generated loggers as the language baseline → [Chapter 01 — foundations §10 Source generators](./01-foundations.md#10-source-generators) (chapter-level ownership in [`coverage-map.md`](../coverage-map.md#chapter-01-foundations)). +- Logging primitives, `ILogger`, and source-generated loggers as the language baseline → [Chapter 01 — foundations §10 Source generators](./01-foundations.md#10-source-generators) (chapter-level ownership in [`coverage-map.md`](../coverage-map.md#chapter-01--foundations)). --- diff --git a/docs/decision-trees.md b/docs/decision-trees.md index ed58f01..67bd29a 100644 --- a/docs/decision-trees.md +++ b/docs/decision-trees.md @@ -139,7 +139,7 @@ flowchart TD D -->|Mixed, want first paint fast
then offload| H[Interactive Auto
only if both modes supported — ch07] ``` -References: [`docs/07-client.md#part-1--blazor-unified-web-app-model`](./07-client.md#part-1-blazor-unified-web-app-model). +References: [`docs/07-client.md#part-1--blazor-unified-web-app-model`](./07-client.md#part-1--blazor-unified-web-app-model). --- @@ -167,7 +167,7 @@ flowchart TD D -->|One platform dominates,
platform-only APIs| I[Native SDK — ch07] ``` -References: [`docs/07-client.md#part-2--maui-net-10`](./07-client.md#part-2-maui-net-10). +References: [`docs/07-client.md#part-2--maui-net-10`](./07-client.md#part-2--maui-net-10). --- @@ -244,7 +244,7 @@ flowchart TD F -->|No| C ``` -References: [`docs/05-performance.md#8-jit--aot`](./05-performance.md#8-jit-aot). +References: [`docs/05-performance.md#8-jit--aot`](./05-performance.md#8-jit--aot). --- @@ -290,7 +290,7 @@ flowchart TD D -->|No, multiple endpoints / policies| C ``` -References: [`docs/01-foundations.md#5-di--lifetimes`](./01-foundations.md#5-di-lifetimes), [`docs/02-aspnetcore.md#11-httpclient`](./02-aspnetcore.md#11-httpclient). +References: [`docs/01-foundations.md#5-di--lifetimes`](./01-foundations.md#5-di--lifetimes), [`docs/02-aspnetcore.md#11-httpclient`](./02-aspnetcore.md#11-httpclient). --- @@ -370,7 +370,7 @@ flowchart TD F -->|Yes| G[BestEffort — never in prod — ch06] ``` -References: [`docs/06-cloud-native.md#3-kubernetes--aks--probes-limits-gc-shutdown`](./06-cloud-native.md#3-kubernetes-aks-probes-limits-gc-shutdown). +References: [`docs/06-cloud-native.md#3-kubernetes--aks--probes-limits-gc-shutdown`](./06-cloud-native.md#3-kubernetes--aks--probes-limits-gc-shutdown). --- @@ -428,7 +428,7 @@ flowchart TD F -->|No| H[Configure connection from
cluster config / Key Vault — ch06 §4] ``` -References: [`docs/06-cloud-native.md#1-net-aspire--what-it-is-what-it-isnt`](./06-cloud-native.md#1-net-aspire-what-it-is-what-it-isnt). +References: [`docs/06-cloud-native.md#1-net-aspire--what-it-is-what-it-isnt`](./06-cloud-native.md#1-net-aspire--what-it-is-what-it-isnt). --- diff --git a/patterns/patterns.md b/patterns/patterns.md index 99d0528..e889f7f 100644 --- a/patterns/patterns.md +++ b/patterns/patterns.md @@ -11,9 +11,9 @@ Decision-bearing prose lives in the chapters; this page only points there. ## Foundations - **DI & lifetimes, keyed services** — Compose at the host, resolve via the container, prefer `GetRequiredKeyedService` over hand-rolled factories. - See [docs/01-foundations.md §5 DI & lifetimes](../docs/01-foundations.md#5-di-lifetimes). + See [docs/01-foundations.md §5 DI & lifetimes](../docs/01-foundations.md#5-di--lifetimes). - **Options pattern, validated, fail-fast** — Bind config sections to typed options, validate on start, prefer the `[OptionsValidator]` source generator. - See [docs/01-foundations.md §6 Configuration & options pattern](../docs/01-foundations.md#6-configuration-options-pattern). + See [docs/01-foundations.md §6 Configuration & options pattern](../docs/01-foundations.md#6-configuration--options-pattern). - **Source generators for hot paths** — `LoggerMessage`, `JsonSerializable`, `GeneratedRegex`, configuration binder, OpenAPI — all eliminate startup reflection and are AOT-safe. See [docs/01-foundations.md §10 Source generators](../docs/01-foundations.md#10-source-generators). - **`IAsyncDisposable` discipline** — Use `await using` for async resources; never mix sync `Dispose` with async resources or shutdown blocks. @@ -21,35 +21,35 @@ Decision-bearing prose lives in the chapters; this page only points there. - **Result types for expected failures** — Exceptions for *exceptional* (programmer error, infra failure), `Result` for *expected* (validation, business rules). See [docs/01-foundations.md §8 Exceptions](../docs/01-foundations.md#8-exceptions). - **Immutability & DTO design, value objects, strongly-typed IDs** — `record` for DTOs, `readonly record struct` for hot-path values, source-generated wrappers (Vogen / StronglyTypedId) for primitive obsession. - See [docs/01-foundations.md §9 Immutability & DTO design](../docs/01-foundations.md#9-immutability-dto-design). + See [docs/01-foundations.md §9 Immutability & DTO design](../docs/01-foundations.md#9-immutability--dto-design). - **`TimeProvider` in production code** — Inject `TimeProvider` (never `DateTime.UtcNow` directly) so handlers, schedulers, and token validation are testable. - See [docs/01-foundations.md §5 DI & lifetimes](../docs/01-foundations.md#5-di-lifetimes) and the testing counterpart [docs/04-testing.md §14 Time](../docs/04-testing.md#time-use-timeprovider-net-8). + See [docs/01-foundations.md §5 DI & lifetimes](../docs/01-foundations.md#5-di--lifetimes) and the testing counterpart [docs/04-testing.md §14 Time](../docs/04-testing.md#time--use-timeprovider-net-8). ## ASP.NET Core - **`ProblemDetails` + `IExceptionHandler`** — Standardise error responses via `AddProblemDetails`; chain multiple `IExceptionHandler`s instead of a single catch-all middleware. - See [docs/02-aspnetcore.md §3 ProblemDetails & Error Handling](../docs/02-aspnetcore.md#3-problemdetails-error-handling). + See [docs/02-aspnetcore.md §3 ProblemDetails & Error Handling](../docs/02-aspnetcore.md#3-problemdetails--error-handling). - **Resilience pipeline (Polly v8)** — Use the standard resilience handler on `HttpClient`; do not hand-roll retry/circuit-breaker loops. - See [docs/02-aspnetcore.md §7 Resilience](../docs/02-aspnetcore.md#7-resilience) and [docs/06-cloud-native.md §6 Resilience](../docs/06-cloud-native.md#6-resilience-see-ch02-7). + See [docs/02-aspnetcore.md §7 Resilience](../docs/02-aspnetcore.md#7-resilience) and [docs/06-cloud-native.md §6 Resilience](../docs/06-cloud-native.md#6-resilience--polly-v8-standard-pipelines-not-hand-rolled-retries). - **Output caching** — Prefer the `OutputCache` middleware for response-shaped reuse; data-shaped reuse belongs in the data caching matrix below. See [docs/02-aspnetcore.md §9 Output Caching](../docs/02-aspnetcore.md#9-output-caching). - **`BackgroundService` + `Channel`** — Long-running in-proc work runs as `BackgroundService`; use bounded `Channel` (FullMode = Wait) for backpressure between producer and worker. See [docs/02-aspnetcore.md §12 Background Work](../docs/02-aspnetcore.md#12-background-work). - **Chain of Responsibility = ASP.NET Core middleware** — `app.UseX()` is the canonical CoR pipeline; for exceptions specifically, register multiple `IExceptionHandler`s. - See [docs/02-aspnetcore.md §3 ProblemDetails & Error Handling](../docs/02-aspnetcore.md#3-problemdetails-error-handling). + See [docs/02-aspnetcore.md §3 ProblemDetails & Error Handling](../docs/02-aspnetcore.md#3-problemdetails--error-handling). ## Data - **Repository — only for aggregate roots** — One repository per aggregate root (Evans / Vernon DDD), not per table; `IOrderRepository`, never `IOrderLineRepository`. - See [docs/03-data.md §11 Repository / Specification](../docs/03-data.md#11-repository-specification). + See [docs/03-data.md §11 Repository / Specification](../docs/03-data.md#11-repository--specification). - **Unit of Work = `DbContext`** — EF Core's `ChangeTracker` *is* the Unit of Work; do not wrap it in a custom `IUnitOfWork`. Share the scoped `DbContext` for cross-repo transactional behaviour. - See [docs/03-data.md §6 Transactions & Unit of Work](../docs/03-data.md#6-transactions-unit-of-work). + See [docs/03-data.md §6 Transactions & Unit of Work](../docs/03-data.md#6-transactions--unit-of-work). - **Specification pattern** — Reusable EF query criteria via Ardalis.Specification; only adopt when ≥3 places ask the same question. - See [docs/03-data.md §11 Repository / Specification](../docs/03-data.md#11-repository-specification). + See [docs/03-data.md §11 Repository / Specification](../docs/03-data.md#11-repository--specification). - **CQRS only where asymmetry exists** — Adopt read-model projections only when reads and writes differ in scaling, storage, or consistency; otherwise it is theatre. See [docs/03-data.md §12 CQRS-lite](../docs/03-data.md#12-cqrs-lite). - **Outbox + idempotent handlers** — Never publish from inside `SaveChangesAsync`; write to an outbox in the same transaction and dispatch from a relay. Idempotency keys on every external command. - See [docs/03-data.md §6 Transactions & Unit of Work — Cross-system consistency → Outbox](../docs/03-data.md#cross-system-consistency-outbox). + See [docs/03-data.md §6 Transactions & Unit of Work — Cross-system consistency → Outbox](../docs/03-data.md#cross-system-consistency--outbox). - **Caching matrix (data side)** — Pick the right cache (in-memory, hybrid, distributed) per access pattern; data caches are not the same decision as `OutputCache`. See [docs/03-data.md §14 Caching](../docs/03-data.md#14-caching) and the response-shape counterpart [docs/02-aspnetcore.md §9 Output Caching](../docs/02-aspnetcore.md#9-output-caching). @@ -60,9 +60,9 @@ Decision-bearing prose lives in the chapters; this page only points there. - **Testcontainers** — Real Postgres / SQL Server / Redis in Docker per fixture; faster and more correct than in-memory providers. See [docs/04-testing.md §6 Testcontainers](../docs/04-testing.md#6-testcontainers). - **`TimeProvider` / `FakeTimeProvider` in tests** — Drive time deterministically with `FakeTimeProvider.Advance`; never `Thread.Sleep`. - See [docs/04-testing.md §14 Time](../docs/04-testing.md#time-use-timeprovider-net-8). + See [docs/04-testing.md §14 Time](../docs/04-testing.md#time--use-timeprovider-net-8). - **Snapshot / approval testing** — Use Verify for response-shape and DI-graph snapshots; commit `*.verified.txt`. - See [docs/04-testing.md §7 Snapshot / approval testing with Verify](../docs/04-testing.md#7-snapshot-approval-testing-with-verify). + See [docs/04-testing.md §7 Snapshot / approval testing with Verify](../docs/04-testing.md#7-snapshot--approval-testing-with-verify). - **Architecture tests** — NetArchTest / ArchUnitNET to enforce layer boundaries (e.g. Domain has no EF Core dependency). See [docs/04-testing.md §9 Architecture tests](../docs/04-testing.md#9-architecture-tests). - **Composition root + DI graph test** — Resolve every registered service through the test host to fail fast on missing or cyclical registrations. @@ -71,11 +71,11 @@ Decision-bearing prose lives in the chapters; this page only points there. ## Cloud-native - **Aspire `ServiceDefaults`** — Shared OTel, health-check, resilience, and discovery wire-up; every service references the same `ServiceDefaults` project. - See [docs/06-cloud-native.md §1 .NET Aspire](../docs/06-cloud-native.md#1-net-aspire-what-it-is-what-it-isnt). + See [docs/06-cloud-native.md §1 .NET Aspire](../docs/06-cloud-native.md#1-net-aspire--what-it-is-what-it-isnt). - **Health checks — three endpoints** — `/health/startup`, `/health/live`, `/health/ready` mapped to K8s probes; what `MapDefaultEndpoints` ships is *not* enough. - See [docs/06-cloud-native.md §10 Health checks](../docs/06-cloud-native.md#10-health-checks-three-endpoints-for-k8s-not-what-servicedefaults-gives-you). + See [docs/06-cloud-native.md §10 Health checks](../docs/06-cloud-native.md#10-health-checks--three-endpoints-for-k8s-not-what-servicedefaults-gives-you). - **Graceful shutdown** — Set `HostOptions.ShutdownTimeout` larger than the longest in-flight request; drain on `ApplicationStopping`, not on `StopAsync`. - See [docs/06-cloud-native.md §11 Graceful shutdown](../docs/06-cloud-native.md#11-graceful-shutdown-drain-dont-drop). + See [docs/06-cloud-native.md §11 Graceful shutdown](../docs/06-cloud-native.md#11-graceful-shutdown--drain-dont-drop). - **Telemetry-by-construction** — Every cross-boundary type takes `ILogger`, owns an `ActivitySource`, emits a `Meter`; bake it in, do not retrofit after incidents. See [docs/06-cloud-native.md §5 Observability](../docs/06-cloud-native.md#5-observability-opentelemetry-one-sdk-three-signals). @@ -86,7 +86,7 @@ Decision-bearing prose lives in the chapters; this page only points there. A short pointer list — each pattern's idiomatic .NET-10 form, and which BCL primitive replaces the hand-roll. Use this as a vocabulary cheat-sheet, not as a design checklist. -- **Strategy** — Inject the algorithm via DI; pick at runtime with `IServiceProvider.GetRequiredKeyedService` or `[FromKeyedServices("name")]`. See [docs/01-foundations.md §5](../docs/01-foundations.md#5-di-lifetimes). +- **Strategy** — Inject the algorithm via DI; pick at runtime with `IServiceProvider.GetRequiredKeyedService` or `[FromKeyedServices("name")]`. See [docs/01-foundations.md §5](../docs/01-foundations.md#5-di--lifetimes). - **Decorator** — Wrap an interface with [Scrutor](https://github.com/khellang/Scrutor) `Decorate`; AOT-safe alternative is a source generator. **Don't** use `DispatchProxy` under AOT. - **Adapter** — Hand-write the adapter at the boundary; it documents the seam better than reflection mappers. - **Factory / Abstract Factory** — `IServiceProvider.GetRequiredKeyedService(key)` replaces most factory interfaces; reach for `Func` only when the key is computed. @@ -95,7 +95,7 @@ Use this as a vocabulary cheat-sheet, not as a design checklist. - **Observer** — `Channel` (with backpressure) or MediatR notifications for in-proc fan-out; skip `IObservable` unless you need Rx operators. - **Mediator** — [MediatR](https://github.com/jbogard/MediatR) for in-proc dispatch; [Wolverine](https://wolverinefx.net/) when you also want messaging + outbox in the same primitive. Not a service locator. - **Command** — Same shape as Mediator's request — the *write* side returns an ID or `Result<…>`, never query data back. See CQRS index entry. -- **Chain of Responsibility** — ASP.NET Core middleware *is* CoR; for exceptions, register multiple `IExceptionHandler`s. See [docs/02-aspnetcore.md §3](../docs/02-aspnetcore.md#3-problemdetails-error-handling). +- **Chain of Responsibility** — ASP.NET Core middleware *is* CoR; for exceptions, register multiple `IExceptionHandler`s. See [docs/02-aspnetcore.md §3](../docs/02-aspnetcore.md#3-problemdetails--error-handling). - **Template Method** — `sealed override` on the framework hook + `abstract` on the customisation point. `BackgroundService.ExecuteAsync` is the canonical example. - **Visitor** — C# pattern matching on a sealed hierarchy; mark the base `abstract` and all derivations `sealed` so the compiler warns on missing arms. - **Specification** — See the data-chapter index entry above. @@ -103,7 +103,7 @@ Use this as a vocabulary cheat-sheet, not as a design checklist. - **Memento** — Snapshot via `System.Text.Json` source-gen so the snapshot is a `byte[]`. - **Composite** — The BCL already composes most things you need (`IConfiguration`, `LoggerProvider`, `CompositeFileProvider`). - **Proxy** — Castle DynamicProxy on JIT only; AOT-safe alternative is a Roslyn source generator. Decorator-via-Scrutor covers most "logging around an interface" cases without proxies. -- **Flyweight** — `ArrayPool`, `MemoryPool`, `string.Intern` / `StringPool`. Hand-roll only after a profiler shows allocation pressure. See [docs/05-performance.md §2](../docs/05-performance.md#2-allocations-the-silent-latency-tax). +- **Flyweight** — `ArrayPool`, `MemoryPool`, `string.Intern` / `StringPool`. Hand-roll only after a profiler shows allocation pressure. See [docs/05-performance.md §2](../docs/05-performance.md#2-allocations--the-silent-latency-tax). - **Bridge** — `ILogger` ↔ `ILoggerProvider` is the canonical .NET example; the abstraction and the implementation vary independently. - **Iterator** — `yield return` for sync, `IAsyncEnumerable` + `[EnumeratorCancellation]` for async. **Don't** materialise to `List` unless the caller needs random access. - **State** — [Stateless](https://github.com/dotnet-state-machine/stateless) for non-trivial workflows; an `enum` + `switch` is enough for two states — don't pull in a state-machine library.