architecture-decision-records
Architecture Decision Records
Document significant architectural and technical decisions using the ADR format to preserve context, rationale, and consequences for future reference.
When to Use This Skill
- Making significant architectural decisions
- Choosing between technology options
- Establishing coding standards or patterns
- Documenting why something was built a certain way
- Onboarding new team members to understand past decisions
- Reviewing or reconsidering previous decisions
What is an ADR?
An Architecture Decision Record (ADR) is a document that captures an important architectural decision along with its context and consequences. ADRs help teams:
- Remember why decisions were made
- Communicate decisions to stakeholders
- Onboard new team members faster
- Reconsider decisions when context changes
ADR Workflow
When to Write an ADR
Write an ADR when:
- Choosing a framework, library, or tool
- Deciding on an architectural pattern
- Establishing a coding convention
- Making a trade-off between competing concerns
- Deviating from common practices
- The decision will be hard to reverse
Decision Process
1. Identify decision needed
2. Research options
3. Draft ADR with proposed decision
4. Review with team
5. Accept or revise
6. Implement decision
7. Update ADR if needed
ADR Lifecycle
┌──────────┐ ┌──────────┐ ┌────────────┐
│ Proposed │ ──▶ │ Accepted │ ──▶ │ Deprecated │
└──────────┘ └──────────┘ └────────────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌────────────┐ ┌────────────┐
│ Rejected │ │ Superseded │ │ (by new │
└──────────┘ └────────────┘ │ ADR) │
└────────────┘
ADR Format
Standard Template
# ADR-{NUMBER}: {TITLE}
## Status
{Proposed | Accepted | Deprecated | Superseded by ADR-XXX}
## Date
{YYYY-MM-DD}
## Context
{Describe the situation and the forces at play. What is the issue that is
motivating this decision? What are the constraints?}
## Decision
{State the decision that was made. Use active voice: "We will..."}
## Consequences
### Positive
- {Benefit 1}
- {Benefit 2}
### Negative
- {Drawback 1}
- {Drawback 2}
### Neutral
- {Side effect that is neither clearly good nor bad}
## Alternatives Considered
### {Alternative 1}
{Description and why it was not chosen}
### {Alternative 2}
{Description and why it was not chosen}
## References
- {Link to relevant documentation}
- {Link to related ADRs}
Directory Structure
docs/
└── adr/
├── README.md # Index of all ADRs
├── 0001-record-architecture-decisions.md
├── 0002-use-python-for-cli.md
├── 0003-choose-typer-for-cli-framework.md
├── 0004-use-pydantic-for-validation.md
└── template.md # ADR template
Example ADRs
Example 1: Technology Choice
# ADR-0003: Use Typer for CLI Framework
## Status
Accepted
## Date
2026-01-15
## Context
We need to build a command-line interface for the infrastructure orchestrator.
The CLI needs to:
- Support subcommands (module, workflow, config)
- Provide helpful error messages and documentation
- Be maintainable and testable
- Integrate well with our Python codebase
Our team has experience with both argparse and Click. We want a modern solution
that minimizes boilerplate while providing good developer experience.
## Decision
We will use Typer as our CLI framework.
Typer is built on top of Click but leverages Python type hints for a more
concise and type-safe API. It provides automatic help generation, shell
completion, and integrates well with Pydantic for validation.
## Consequences
### Positive
- Reduced boilerplate compared to argparse and Click
- Type hints serve as both documentation and validation
- Automatic generation of help text and shell completions
- Built on mature Click ecosystem
- Rich terminal output support via optional dependency
- Easy testing with CliRunner
### Negative
- Additional dependency (though lightweight)
- Team needs to learn Typer-specific patterns
- Less flexibility than raw Click for edge cases
### Neutral
- Requires Python 3.7+ (we already require 3.11+)
## Alternatives Considered
### argparse (standard library)
Built into Python, no external dependencies. However, requires significant
boilerplate for subcommands and has poor developer experience for complex CLIs.
Rejected due to verbosity.
### Click
Mature and widely used. Typer is built on Click, so we get Click's reliability
with less code. Rejected in favor of Typer's more concise API.
### Fire
Very minimal boilerplate - just annotate functions. However, less control over
help text and argument parsing. Rejected due to limited customization.
## References
- [Typer Documentation](https://typer.tiangolo.com/)
- [Click Documentation](https://click.palletsprojects.com/)
- ADR-0002: Use Python for CLI
Example 2: Architectural Pattern
# ADR-0005: Use Dependency Injection for Service Layer
## Status
Accepted
## Date
2026-01-20
## Context
Our orchestrator service layer has grown to include multiple services that
depend on each other:
- ModuleService depends on ConfigService and ValidationService
- WorkflowService depends on ModuleService and ExecutionService
- All services need access to configuration and logging
Currently, services instantiate their dependencies directly, making testing
difficult and coupling high. We need a pattern that:
- Enables unit testing with mocks
- Reduces coupling between services
- Supports different configurations (dev/prod)
- Remains simple and understandable
## Decision
We will use constructor-based dependency injection without a DI framework.
Services will receive their dependencies through their constructors. A simple
factory module will handle wiring for production use. Tests will inject mocks
directly.
```python
class ModuleService:
def __init__(
self,
config_service: ConfigService,
validation_service: ValidationService,
) -> None:
self._config = config_service
self._validation = validation_service
Consequences
Positive
- Clear dependencies visible in constructor
- Easy to test with mock objects
- No framework learning curve
- Explicit over implicit wiring
- Works well with type checkers
Negative
- Manual wiring in factory can become verbose
- No automatic lifecycle management
- Must remember to update factory when adding dependencies
Neutral
- Team must agree on where wiring happens
- May eventually need a DI container if complexity grows
Alternatives Considered
Service Locator Pattern
Services request dependencies from a global registry. Rejected because dependencies are hidden inside methods rather than explicit in constructor.
Dependency Injection Framework (dependency-injector)
Full-featured DI container with auto-wiring. Rejected as overkill for our current size. Can reconsider if manual wiring becomes painful.
Singleton Services
Each service is a module-level singleton. Rejected because it makes testing difficult and hides dependencies.
References
### Example 3: Standard/Convention
```markdown
# ADR-0007: Use Conventional Commits for Git Messages
## Status
Accepted
## Date
2026-01-25
## Context
Our git history lacks consistency. Commit messages vary widely in format:
- "fixed bug"
- "WIP"
- "Update module.py"
- "JIRA-123: Implement feature X with detailed description"
This makes it difficult to:
- Generate changelogs automatically
- Understand what changed at a glance
- Search history effectively
- Trigger CI/CD based on change type
## Decision
We will adopt the Conventional Commits specification for all commit messages.
Format: `<type>(<scope>): <description>`
Types:
- `feat`: New feature
- `fix`: Bug fix
- `docs`: Documentation only
- `style`: Formatting, no code change
- `refactor`: Code change that neither fixes nor adds
- `test`: Adding or updating tests
- `chore`: Maintenance tasks
Examples:
- `feat(cli): add module list command`
- `fix(workflow): handle empty dependency list`
- `docs: update README with installation steps`
- `refactor(services): extract validation logic`
## Consequences
### Positive
- Consistent, readable git history
- Automatic changelog generation possible
- Clear indication of change scope and impact
- Enables semantic versioning automation
- Easier code review
### Negative
- Learning curve for team members unfamiliar with format
- Slightly more effort per commit
- Need to enforce via tooling or review
### Neutral
- Need to decide on allowed scopes
- May want to add commit message linting
## Alternatives Considered
### Free-form Messages with Guidelines
Provide guidelines but don't enforce format. Rejected because guidelines
without enforcement lead to drift over time.
### Gitmoji
Use emojis to categorize commits (🐛 for bug, ✨ for feature). Rejected as
less tool-friendly and harder to type in terminal.
## References
- [Conventional Commits](https://www.conventionalcommits.org/)
- [Semantic Versioning](https://semver.org/)
- [commitlint](https://commitlint.js.org/)
ADR Index Template
# Architecture Decision Records
This directory contains Architecture Decision Records (ADRs) for the project.
## What is an ADR?
An ADR captures an important architectural decision along with its context
and consequences.
## ADR Index
| Number | Title | Status | Date |
|--------|-------|--------|------|
| [ADR-0001](0001-record-architecture-decisions.md) | Record Architecture Decisions | Accepted | 2026-01-10 |
| [ADR-0002](0002-use-python-for-cli.md) | Use Python for CLI | Accepted | 2026-01-12 |
| [ADR-0003](0003-choose-typer-for-cli-framework.md) | Use Typer for CLI Framework | Accepted | 2026-01-15 |
| [ADR-0004](0004-use-pydantic-for-validation.md) | Use Pydantic for Validation | Accepted | 2026-01-18 |
| [ADR-0005](0005-dependency-injection-pattern.md) | Use Dependency Injection | Accepted | 2026-01-20 |
## Creating a New ADR
1. Copy `template.md` to `NNNN-title-with-dashes.md`
2. Fill in all sections
3. Set status to "Proposed"
4. Submit for review
5. Update status to "Accepted" or "Rejected" after review
6. Update this index
## Superseding an ADR
When a decision is changed:
1. Create a new ADR with the new decision
2. Update the old ADR's status to "Superseded by ADR-NNNN"
3. Reference the old ADR in the new one
Creating ADRs
Step-by-Step Process
-
Identify the decision
# Create new ADR file cp docs/adr/template.md docs/adr/0006-your-decision-title.md -
Write the Context
- What problem are you solving?
- What constraints exist?
- What forces are at play?
-
Research Alternatives
- List at least 2-3 options
- Evaluate pros and cons of each
- Document why each was considered
-
State the Decision
- Use active voice: "We will..."
- Be specific and unambiguous
- Include code examples if helpful
-
Document Consequences
- What are the benefits?
- What are the drawbacks?
- What trade-offs are we making?
-
Review with Team
- Share draft for feedback
- Discuss alternatives
- Reach consensus
-
Finalize
- Update status to Accepted/Rejected
- Update the ADR index
- Commit to repository
Resources
Guidelines
- Keep ADRs short and focused (1-2 pages)
- Write for future readers who lack context
- Include enough detail to understand the decision
- Don't include implementation details
- Update status rather than deleting ADRs
- Reference related ADRs when relevant
- Use consistent numbering (0001, 0002, etc.)
- Store ADRs in version control with code