Problem
Current State:
This project has made several significant architectural decisions (SQLite over other databases, Repository + Service pattern, AutoMapper, FluentValidation, in-memory caching, stadium-themed versioning), but the rationale, context, and trade-offs behind these decisions are not formally documented.
Pain Points:
-
Learning Barrier: As a learning PoC emphasizing "clarity and best practices," new developers or learners cannot easily understand WHY certain architectural choices were made, only WHAT was implemented.
-
Lost Context: Over time, the motivation behind decisions becomes unclear. Future contributors may:
- Blindly accept outdated decisions that should be revisited
- Reverse decisions without understanding their consequences
- Repeat past mistakes by not knowing what was already considered and rejected
-
Onboarding Friction: New contributors must reverse-engineer decisions from code, commit history, or incomplete documentation rather than reading clear rationale.
-
Decision Reversals: When requirements change (e.g., moving from SQLite to PostgreSQL for production), there's no record of:
- What alternatives were considered
- What trade-offs were made
- What circumstances would warrant revisiting the decision
Proposed Solution
Implement Architecture Decision Records (ADRs) using Michael Nygard's template to document all architecturally significant decisions in a lightweight, maintainable format.
Key Benefits:
- Educational Value: Learners understand not just implementation patterns, but decision-making reasoning
- Knowledge Preservation: Decisions remain accessible as team composition changes
- Context for AI Agents: Structured ADRs provide high-value context for GitHub Copilot and other AI assistants
- Decision Traceability: Clear audit trail of what was decided, when, and why
- Change Confidence: Future changes informed by understanding original constraints and trade-offs
- Best Practice Demonstration: Shows professional decision documentation for a learning resource
Timing: This is particularly valuable now because:
Suggested Approach
1. Repository Structure
Create an adr/ directory at the project root to store all ADRs:
Dotnet.Samples.AspNetCore.WebApi/
├── adr/
│ ├── README.md # ADR index and navigation
│ ├── 0001-adopt-traditional-layered-architecture.md # May be superseded by Issue #266
│ ├── 0002-use-mvc-controllers-over-minimal-api.md
│ ├── 0003-use-sqlite-for-data-storage.md
│ ├── 0004-use-uuid-as-database-primary-key.md
│ ├── 0005-use-squad-number-as-api-mutation-key.md
│ ├── 0006-use-rfc-7807-problem-details-for-errors.md
│ ├── 0007-use-fluentvalidation-over-data-annotations.md
│ ├── 0008-use-automapper-for-dto-mapping.md
│ ├── 0009-implement-in-memory-caching.md
│ ├── 0010-use-serilog-for-structured-logging.md
│ ├── 0011-use-docker-for-containerization.md
│ └── 0012-use-stadium-themed-semantic-versioning.md
├── README.md
└── ...
2. ADR Template (Michael Nygard Format)
# [NUMBER]. [TITLE]
Date: YYYY-MM-DD
## Status
[Proposed | Accepted | Deprecated | Superseded by ADR-XXXX]
## Context
What is the issue we're facing? What forces are at play (technical, political, social, project constraints)? Present facts neutrally without bias.
## Decision
We will [DECISION IN ACTIVE VOICE WITH FULL SENTENCES].
## Consequences
### Positive
- Consequence 1
- Consequence 2
### Negative
- Trade-off 1
- Limitation 1
### Neutral
- Other effect 1
3. Initial ADRs to Create
Retroactive ADRs (document existing decisions, ordered by architectural significance):
- 0001-adopt-traditional-layered-architecture.md: Why traditional layered architecture over Clean Architecture
- 0002-use-mvc-controllers-over-minimal-api.md: Why MVC controllers over ASP.NET Core Minimal API
- Context: Explicit routing via attributes, clear separation of concerns, familiar convention for learners coming from MVC backgrounds, better testability with constructor injection
- Trade-offs: More boilerplate than Minimal API, heavier startup overhead, Minimal API is the direction Microsoft is investing in for new projects
- 0003-use-sqlite-for-data-storage.md: Why SQLite over PostgreSQL/SQL Server
- 0004-use-uuid-as-database-primary-key.md: Why
Guid (UUID v4) as the database primary key instead of a sequential integer
- Context: Avoids leaking record counts via predictable IDs, decouples identity generation from the database, aligns with .NET defaults (
Guid.NewGuid()), supports future distributed or multi-source scenarios
- Trade-offs: Larger storage footprint than
int, random GUIDs can fragment B-tree indexes (less relevant with SQLite), not human-readable; the Id is intentionally never surfaced in API URLs or responses
- 0005-use-squad-number-as-api-mutation-key.md: Why
squadNumber is the route parameter for all mutation endpoints (GET /players/squadNumber/{n}, PUT, DELETE) instead of the internal UUID
- Context:
squadNumber is the natural, human-readable domain identifier for a player within a team; exposing the UUID in URLs would couple API consumers to an internal database concern and provide no domain value
- Trade-offs: Squad numbers are unique only within a single team and can be reassigned between seasons; uniqueness must be enforced at the application layer (
BeUniqueSquadNumber validator rule); not suitable as a stable long-term identifier across team or season boundaries
- 0006-use-rfc-7807-problem-details-for-errors.md: Why RFC 7807 Problem Details for all error responses
- Context: Standardised machine-readable error format, consistent shape across all endpoints, built-in support via
TypedResults.Problem / TypedResults.ValidationProblem in ASP.NET Core
- Trade-offs: More verbose than a plain string message, consumers must understand the Problem Details schema
- 0007-use-fluentvalidation-over-data-annotations.md: Why FluentValidation
- Context: Complex validation rules, separation of concerns, testability, explicit rule sets per operation (
Create / Update)
- Trade-offs: Additional dependency, not built-in to ASP.NET Core
- 0008-use-automapper-for-dto-mapping.md: Why AutoMapper vs manual mapping
- Context: Reduce boilerplate, convention-based mapping
- Trade-offs: Magic behavior, runtime performance cost, learning curve
- 0009-implement-in-memory-caching.md: Why
IMemoryCache vs distributed cache
- Context: Demo simplicity, single-instance deployment, fast access
- Trade-offs: Lost on restart, not suitable for multi-instance deployments
- 0010-use-serilog-for-structured-logging.md: Why Serilog over Microsoft.Extensions.Logging
- Context: Structured log output (JSON-friendly), enrichers, multiple sinks (console + file), consistent format across the cross-language comparison projects
- Trade-offs: Additional dependency over the built-in abstractions, configuration is more involved
- 0011-use-docker-for-containerization.md: Why Docker + Docker Compose for local development and distribution
- Context: Consistent dev environment, zero-config onboarding, mirrors production-like setup
- Trade-offs: Requires Docker Desktop, adds a layer of abstraction over the host environment
- 0012-use-stadium-themed-semantic-versioning.md: Why football stadium names
- Context: Memorable releases, alphabetical progression, project theme
- Trade-offs: Non-standard, might confuse newcomers
4. Integration Points
Update CONTRIBUTING.md:
Add section on ADRs:
## Architecture Decision Records
When proposing changes that affect:
- Project structure or architecture
- Technology choices or dependencies
- Non-functional requirements (performance, security, scalability)
- Development workflows or processes
Please create an ADR in the `adr/` directory following the existing template. See `adr/README.md` for guidance.
Update README.md:
Add ADR section to documentation:
## Architecture Decisions
See [Architecture Decision Records (ADRs)](adr/README.md) for documented decisions about this project's architecture, technology choices, and development practices.
Update .github/copilot-instructions.md:
Add ADR context for AI agents:
## Architecture Decision Records (ADRs)
Load `#file:adr/README.md` when:
- User asks about architectural choices or "why we use X"
- Proposing changes to core architecture or dependencies
- Need historical context for past decisions
5. Implementation Steps
- Create
adr/ directory structure
- Write
adr/README.md with index, navigation, and template
- Create retroactive ADRs 0001-0012 for existing decisions
- Update CONTRIBUTING.md, README.md, and Copilot instructions
- Add ADR creation to PR review checklist
Important: ADR 0001 (Traditional Layered Architecture) documents the current state with full awareness that Issue #266 proposes a future change to Clean Architecture. This demonstrates proper ADR usage:
- Capture the original decision with its context
- Mark it as "Under Reconsideration"
- When Clean Architecture is adopted, create a new ADR that supersedes 0001
- Keep 0001 in the repository for historical context
6. Tooling (Optional Future Enhancement)
Consider adopting adr-tools or similar:
adr new "Use PostgreSQL for production": Generate new ADR with boilerplate
adr list: Show all ADRs with status
adr supersede 0003 "Use PostgreSQL": Link superseded decisions
For now, manual markdown files are sufficient given the project's simplicity.
Acceptance Criteria
References
ADR Resources
Related Project Documentation
#file:.github/copilot-instructions.md - Current AI agent instruction strategy
#file:CONTRIBUTING.md - Contribution guidelines to be extended with ADR process
#file:CHANGELOG.md - Shows evolution of decisions over time (e.g., .NET 10 upgrade)
#file:README.md - Project overview highlighting "clarity and best practices" focus
Related GitHub Issues
These open issues involve architectural decisions that would benefit from ADR documentation:
Note: Implementing ADRs now will establish the practice before these major architectural changes, ensuring decision rationale is captured from the start.
Example ADR Repositories
Tooling
Problem
Current State:
This project has made several significant architectural decisions (SQLite over other databases, Repository + Service pattern, AutoMapper, FluentValidation, in-memory caching, stadium-themed versioning), but the rationale, context, and trade-offs behind these decisions are not formally documented.
Pain Points:
Learning Barrier: As a learning PoC emphasizing "clarity and best practices," new developers or learners cannot easily understand WHY certain architectural choices were made, only WHAT was implemented.
Lost Context: Over time, the motivation behind decisions becomes unclear. Future contributors may:
Onboarding Friction: New contributors must reverse-engineer decisions from code, commit history, or incomplete documentation rather than reading clear rationale.
Decision Reversals: When requirements change (e.g., moving from SQLite to PostgreSQL for production), there's no record of:
Proposed Solution
Implement Architecture Decision Records (ADRs) using Michael Nygard's template to document all architecturally significant decisions in a lightweight, maintainable format.
Key Benefits:
Timing: This is particularly valuable now because:
Suggested Approach
1. Repository Structure
Create an
adr/directory at the project root to store all ADRs:2. ADR Template (Michael Nygard Format)
3. Initial ADRs to Create
Retroactive ADRs (document existing decisions, ordered by architectural significance):
Guid(UUID v4) as the database primary key instead of a sequential integerGuid.NewGuid()), supports future distributed or multi-source scenariosint, random GUIDs can fragment B-tree indexes (less relevant with SQLite), not human-readable; theIdis intentionally never surfaced in API URLs or responsessquadNumberis the route parameter for all mutation endpoints (GET /players/squadNumber/{n},PUT,DELETE) instead of the internal UUIDsquadNumberis the natural, human-readable domain identifier for a player within a team; exposing the UUID in URLs would couple API consumers to an internal database concern and provide no domain valueBeUniqueSquadNumbervalidator rule); not suitable as a stable long-term identifier across team or season boundariesTypedResults.Problem/TypedResults.ValidationProblemin ASP.NET CoreCreate/Update)IMemoryCachevs distributed cache4. Integration Points
Update CONTRIBUTING.md:
Add section on ADRs:
Update README.md:
Add ADR section to documentation:
Update .github/copilot-instructions.md:
Add ADR context for AI agents:
5. Implementation Steps
adr/directory structureadr/README.mdwith index, navigation, and templateImportant: ADR 0001 (Traditional Layered Architecture) documents the current state with full awareness that Issue #266 proposes a future change to Clean Architecture. This demonstrates proper ADR usage:
6. Tooling (Optional Future Enhancement)
Consider adopting adr-tools or similar:
adr new "Use PostgreSQL for production": Generate new ADR with boilerplateadr list: Show all ADRs with statusadr supersede 0003 "Use PostgreSQL": Link superseded decisionsFor now, manual markdown files are sufficient given the project's simplicity.
Acceptance Criteria
adr/directory created at project rootadr/README.mdcreated with ADR index, template, and usage guide.github/copilot-instructions.mdupdated to include ADR context loading guidanceReferences
ADR Resources
Related Project Documentation
#file:.github/copilot-instructions.md- Current AI agent instruction strategy#file:CONTRIBUTING.md- Contribution guidelines to be extended with ADR process#file:CHANGELOG.md- Shows evolution of decisions over time (e.g., .NET 10 upgrade)#file:README.md- Project overview highlighting "clarity and best practices" focusRelated GitHub Issues
These open issues involve architectural decisions that would benefit from ADR documentation:
Note: Implementing ADRs now will establish the practice before these major architectural changes, ensuring decision rationale is captured from the start.
Example ADR Repositories
Tooling