From 44d21d6c95a78968c5f13444faac69ee533768c1 Mon Sep 17 00:00:00 2001 From: mghabin <81494213+MohammadGhabin@users.noreply.github.com> Date: Thu, 30 Apr 2026 18:54:23 +0300 Subject: [PATCH] docs: pointer-only patterns index, ch01 per-block source rebalance, ch06 anchor fixes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/01-foundations.md | 26 ++++++--- patterns/patterns.md | 130 ++++++++++++++++------------------------- 2 files changed, 69 insertions(+), 87 deletions(-) diff --git a/docs/01-foundations.md b/docs/01-foundations.md index d316c42..3e28e85 100644 --- a/docs/01-foundations.md +++ b/docs/01-foundations.md @@ -97,6 +97,7 @@ libs; you will want it the first time prod stack-traces hit a NuGet'd package. - [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. +- [`dotnet/msbuild` — repository](https://github.com/dotnet/msbuild) — primary source for MSBuild evaluation, `Directory.Build.props` discovery, and `ContinuousIntegrationBuild` handling. - [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). @@ -241,16 +242,16 @@ method does. - [What's new in C# 14](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-14) — extension members, `field` keyword (GA), null-conditional assignment, partial ctors/events, first-class Span conversions, lambda parameter modifiers. - [What's new in C# 13](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-13) — `params` collections, `ref readonly`, partial properties, new `Lock` type, `\e` escape, implicit `^` index in initializers. -- [`OverloadResolutionPriority` (C# 13)](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-13#overload-resolution-priority) — disambiguate new (Span-based) overloads from existing ones without breaking source. -- [`allows ref struct` (C# 13)](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-13#allows-ref-struct) — ref struct anti-constraint and ref-struct-implements-interface. -- [`field` keyword (C# 14)](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-14#the-field-keyword) — backing-field access in property accessors. -- [`System.Threading.Lock` (C# 13)](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-13#new-lock-object) — when the compiler rewrites `lock` to `EnterScope()`. +- [`OverloadResolutionPriority` proposal (`dotnet/csharplang`)](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/overload-resolution-priority.md) — primary-source design for disambiguating new (Span-based) overloads from existing ones without breaking source. +- [`allows ref struct` proposal (`dotnet/csharplang`)](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/ref-struct-interfaces.md) — primary-source design for the ref-struct anti-constraint and ref-struct-implements-interface. +- [`field` keyword proposal (`dotnet/csharplang`)](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-14.0/field-keyword.md) — primary-source design for backing-field access in property accessors. - [Primary constructors tutorial](https://learn.microsoft.com/dotnet/csharp/whats-new/tutorials/primary-constructors) — capture semantics, when to promote to fields, struct guidance. - [Collection expressions](https://learn.microsoft.com/dotnet/csharp/language-reference/operators/collection-expressions) — target-typing rules. -- [`params` collections](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/params) — `params ReadOnlySpan` and zero-alloc dispatch. +- [`params` collections proposal (`dotnet/csharplang`)](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/params-collections.md) — primary-source design for `params ReadOnlySpan` and zero-alloc dispatch. - [Records](https://learn.microsoft.com/dotnet/csharp/language-reference/builtin-types/record) — value equality, `with`, when to choose record vs class. - [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`. +- [`System.Threading.Lock` proposal (`dotnet/csharplang`)](https://github.com/dotnet/csharplang/blob/main/proposals/csharp-13.0/lock-object.md) — primary-source design for the compiler rewrite of `lock` to `EnterScope()`. - [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. @@ -464,7 +465,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:** @@ -477,6 +478,9 @@ The cluster-level resilience story (Aspire `ServiceDefaults` wiring of the same - [`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. +- [`dotnet/extensions` — `Microsoft.Extensions.Http.Resilience` source](https://github.com/dotnet/extensions/tree/main/src/Libraries/Microsoft.Extensions.Http.Resilience) — primary source for the standard resilience handler implementation. +- [Polly v8 documentation](https://www.pollydocs.org/) — strategy/pipeline reference and migration notes for the v7→v8 retry/circuit-breaker/timeout shift. +- [David Fowler — *AspNetCoreDiagnosticScenarios*](https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/main/AsyncGuidance.md) — community-canonical DI + async pitfalls (captive dependencies, sync-over-async in handlers). --- @@ -530,6 +534,7 @@ smell that defeats validation, defeats reload, and defeats testability. - [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. +- [`dotnet/runtime` — `Microsoft.Extensions.Options.SourceGeneration`](https://github.com/dotnet/runtime/tree/main/src/libraries/Microsoft.Extensions.Options/gen) — primary-source generator implementation behind `[OptionsValidator]`. --- @@ -563,6 +568,8 @@ always `using`. - [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. +- [`dotnet/runtime` — `IAsyncDisposable` source](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/IAsyncDisposable.cs) — primary-source contract for `DisposeAsync` ordering and `ValueTask` shape. +- [David Fowler — *AspNetCoreDiagnosticScenarios* (Async guidance: disposal)](https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/blob/main/AsyncGuidance.md#dispose-of-cancellationtokensources-used-for-timeouts) — community-canonical pitfalls around `CancellationTokenSource` and async disposal. --- @@ -668,9 +675,10 @@ This is the canonical C# 14 replacement for the `private string _name; public st - [`field` keyword](https://learn.microsoft.com/dotnet/csharp/whats-new/csharp-14#the-field-keyword) — validating accessors without a hand-rolled backing field. - [`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. +- [Eric Lippert — *Immutability in C#* (series)](https://ericlippert.com/2007/10/04/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. +- [`dotnet/runtime` — `System.Collections.Immutable` source](https://github.com/dotnet/runtime/tree/main/src/libraries/System.Collections.Immutable) — primary source for the immutable-collections implementation and structural-sharing trade-offs. --- @@ -725,6 +733,7 @@ runs in the IDE — every dep becomes a load-order hazard). - [`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. +- [Andrew Lock — *Creating a source generator* (series)](https://andrewlock.net/creating-a-source-generator-part-1-creating-an-incremental-source-generator/) — community-canonical walkthrough of incremental source generators, the model the in-box `LoggerMessage` / options / JSON generators follow. --- @@ -885,6 +894,9 @@ next to the csproj; do not let ops set GC policy in the deploy pipeline. - [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`. +- [Maoni Stephens — *Maoni's WebLog* (GC architect)](https://devblogs.microsoft.com/dotnet/author/maoni/) — primary-source posts from the .NET GC architect on Server GC, regions, DATAS, and heap-budget heuristics. +- [`dotnet/runtime` — DATAS design](https://github.com/dotnet/runtime/blob/main/docs/design/features/datas.md) — primary-source design note for Dynamic Adaptation To Application Sizes. +- [`dotnet/runtime` — Threadpool design](https://github.com/dotnet/runtime/tree/main/src/libraries/System.Private.CoreLib/src/System/Threading) — primary-source thread-pool implementation behind the hill-climbing semantics referenced above. --- diff --git a/patterns/patterns.md b/patterns/patterns.md index e889f7f..af8d103 100644 --- a/patterns/patterns.md +++ b/patterns/patterns.md @@ -1,8 +1,6 @@ # Patterns index -Thin index of named patterns used across this guide. -Each entry is a one-paragraph summary with a link to the chapter section that owns the decision. -Decision-bearing prose lives in the chapters; this page only points there. +This is an index. Each pattern is owned and detailed in the linked chapter section; this page only points there. > Companion pages: [`monorepo.md`](./monorepo.md), [`anti-patterns.md`](./anti-patterns.md), shared [`references.md`](./references.md). @@ -10,104 +8,76 @@ 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). -- **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). -- **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. - See [docs/01-foundations.md §7 Disposal](../docs/01-foundations.md#7-disposal). -- **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). -- **`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). +- **DI & lifetimes, keyed services** — see [chapter 01 §5](../docs/01-foundations.md#5-di--lifetimes). +- **Options pattern, validated, fail-fast** — see [chapter 01 §6](../docs/01-foundations.md#6-configuration--options-pattern). +- **Source generators for hot paths** — see [chapter 01 §10](../docs/01-foundations.md#10-source-generators). +- **`IAsyncDisposable` discipline** — see [chapter 01 §7](../docs/01-foundations.md#7-disposal). +- **Result types for expected failures** — see [chapter 01 §8](../docs/01-foundations.md#8-exceptions). +- **Immutability & DTO design, value objects, strongly-typed IDs** — see [chapter 01 §9](../docs/01-foundations.md#9-immutability--dto-design). +- **`TimeProvider` in production code** — see [chapter 01 §5](../docs/01-foundations.md#5-di--lifetimes); test-side counterpart [chapter 04 §14](../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). -- **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). -- **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). +- **`ProblemDetails` + `IExceptionHandler`** — see [chapter 02 §3](../docs/02-aspnetcore.md#3-problemdetails--error-handling). +- **Resilience pipeline (Polly v8)** — see [chapter 02 §7](../docs/02-aspnetcore.md#7-resilience) and [chapter 06 §6](../docs/06-cloud-native.md#6-resilience--see-ch02-7). +- **Output caching** — see [chapter 02 §9](../docs/02-aspnetcore.md#9-output-caching). +- **`BackgroundService` + `Channel`** — see [chapter 02 §12](../docs/02-aspnetcore.md#12-background-work). +- **Chain of Responsibility = ASP.NET Core middleware** — see [chapter 02 §3](../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). -- **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). -- **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). -- **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). -- **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). +- **Repository — only for aggregate roots** — see [chapter 03 §11](../docs/03-data.md#11-repository--specification). +- **Unit of Work = `DbContext`** — see [chapter 03 §6](../docs/03-data.md#6-transactions--unit-of-work). +- **Specification pattern** — see [chapter 03 §11](../docs/03-data.md#11-repository--specification). +- **CQRS-lite** — see [chapter 03 §12](../docs/03-data.md#12-cqrs-lite). +- **Outbox + idempotent handlers** — see [chapter 03 §6 → Cross-system consistency](../docs/03-data.md#cross-system-consistency--outbox). +- **Caching matrix (data side)** — see [chapter 03 §14](../docs/03-data.md#14-caching); response-shape counterpart [chapter 02 §9](../docs/02-aspnetcore.md#9-output-caching). ## Testing -- **`WebApplicationFactory` integration tests** — One spine per app, override `IExternalApi` with WireMock, swap auth via a `TestAuthHandler`. - See [docs/04-testing.md §5 WebApplicationFactory for ASP.NET Core](../docs/04-testing.md#5-webapplicationfactory-for-aspnet-core). -- **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). -- **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). -- **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. - See [docs/04-testing.md §5 WebApplicationFactory for ASP.NET Core](../docs/04-testing.md#5-webapplicationfactory-for-aspnet-core). +- **`WebApplicationFactory` integration tests** — see [chapter 04 §5](../docs/04-testing.md#5-webapplicationfactory-for-aspnet-core). +- **Testcontainers** — see [chapter 04 §6](../docs/04-testing.md#6-testcontainers). +- **`TimeProvider` / `FakeTimeProvider` in tests** — see [chapter 04 §14](../docs/04-testing.md#time--use-timeprovider-net-8). +- **Snapshot / approval testing** — see [chapter 04 §7](../docs/04-testing.md#7-snapshot--approval-testing-with-verify). +- **Architecture tests** — see [chapter 04 §9](../docs/04-testing.md#9-architecture-tests). +- **Composition root + DI graph test** — see [chapter 04 §5](../docs/04-testing.md#5-webapplicationfactory-for-aspnet-core). ## 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). -- **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). -- **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). -- **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). +- **Aspire `ServiceDefaults`** — see [chapter 06 §1](../docs/06-cloud-native.md#1-net-aspire--what-it-is-what-it-isnt). +- **Health checks — three endpoints** — see [chapter 06 §10](../docs/06-cloud-native.md#10-health-checks--three-endpoints-for-k8s-not-what-servicedefaults-gives-you). +- **Graceful shutdown** — see [chapter 06 §11](../docs/06-cloud-native.md#11-graceful-shutdown--drain-dont-drop). +- **Telemetry-by-construction** — see [chapter 06 §5](../docs/06-cloud-native.md#5-observability--opentelemetry-one-sdk-three-signals). --- ## GoF refresher (.NET-idiomatic) 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). -- **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. -- **Builder** — The BCL already gives you `HttpRequestMessage`, `WebApplicationBuilder`, `HostBuilder`. Hand-roll a builder only for *your* domain construction. -- **Singleton** — Always via the DI container (`AddSingleton`); never `static readonly Foo Instance = new()` — the latter is untestable. -- **Observer** — `Channel` (with backpressure) or MediatR notifications for in-proc fan-out; skip `IObservable` unless you need Rx operators. +Vocabulary cheat-sheet, not a design checklist. + +- **Strategy** — algorithm via DI; runtime pick with `IServiceProvider.GetRequiredKeyedService` or `[FromKeyedServices("name")]`. See [chapter 01 §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. `DispatchProxy` is JIT-only. +- **Adapter** — hand-written at the boundary; documents the seam better than reflection mappers. +- **Factory / Abstract Factory** — `IServiceProvider.GetRequiredKeyedService(key)` replaces most factory interfaces; `Func` when the key is computed. +- **Builder** — BCL ships `HttpRequestMessage`, `WebApplicationBuilder`, `HostBuilder`. Hand-roll for *your* domain construction. +- **Singleton** — DI container (`AddSingleton`); `static readonly Foo Instance = new()` is untestable. +- **Observer** — `Channel` (with backpressure) or MediatR notifications for in-proc fan-out; `IObservable` only when 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). +- **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, multiple `IExceptionHandler`s. See [chapter 02 §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. -- **Null Object** — `NullLogger.Instance` is the canonical BCL example; use it for tests and degenerate paths. -- **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). -- **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. -- **Interpreter** — Roslyn, `System.Linq.Expressions`, EF Core's LINQ provider, and [Sprache](https://github.com/sprache/Sprache) cover the realistic cases. If you find yourself writing one, you probably want a typed YAML/JSON config instead. +- **Specification** — see the data-chapter index entry above. +- **Null Object** — `NullLogger.Instance` is the canonical BCL example. +- **Memento** — snapshot via `System.Text.Json` source-gen so the snapshot is a `byte[]`. +- **Composite** — 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. +- **Flyweight** — `ArrayPool`, `MemoryPool`, `string.Intern` / `StringPool`. See [chapter 05 §2](../docs/05-performance.md#2-allocations--the-silent-latency-tax). +- **Bridge** — `ILogger` ↔ `ILoggerProvider` is the canonical .NET example; abstraction and implementation vary independently. +- **Iterator** — `yield return` for sync, `IAsyncEnumerable` + `[EnumeratorCancellation]` for async. +- **State** — [Stateless](https://github.com/dotnet-state-machine/stateless) for non-trivial workflows; an `enum` + `switch` is enough for two states. +- **Interpreter** — Roslyn, `System.Linq.Expressions`, EF Core's LINQ provider, and [Sprache](https://github.com/sprache/Sprache) cover the realistic cases. ---