Architecture Decision Records (ADRs)¶
Architecture Decision Records (ADRs) document the significant architectural choices made during software development. They capture the reasoning behind decisions, the context in which they were made, and the consequences of those choices.
ADRs create a searchable, version-controlled history of architectural evolution that helps current team members understand the system and guides future developers who join the project.
Philosophy
ADRs are lightweight documentation that answers one question: "Why did we make this choice?" They preserve context that would otherwise be lost in Slack conversations, meeting notes, and institutional memory.
Why Use ADRs?¶
The Problem¶
Without ADRs, architectural knowledge exists in:
- Scattered conversations: Slack threads, email chains, hallway discussions
- Meeting notes: Buried in documents, different formats, hard to find
- Individual memories: Team members leave, context is lost
- Code comments: Limited space, becomes outdated, not searchable
- Tribal knowledge: "That's just how we've always done it"
This creates problems:
graph TB
A[Architectural Decision] --> B[Discussed in Slack]
A --> C[Mentioned in Meeting]
A --> D[Implemented in Code]
B --> E[Thread Lost in History]
C --> F[Notes Scattered]
D --> G[Context Missing]
E --> H[New Developer: Why?]
F --> H
G --> H
H --> I[Repeat Same Discussion]
H --> J[Make Conflicting Choice]
H --> K[Cargo Cult Programming]
style A fill:#e1f5ff
style H fill:#ffebee
style I fill:#fff9e1
style J fill:#fff9e1
style K fill:#fff9e1
The Solution¶
ADRs provide:
- Context preservation: Future developers understand the "why" behind decisions
- Team alignment: All stakeholders aware of important choices
- Knowledge management: Searchable history of decisions
- Onboarding: New team members understand system evolution
- Decision quality: Writing forces clear thinking about tradeoffs
ADR Format¶
Michael Nygard Template¶
We use Michael Nygard's lightweight template, which balances simplicity with completeness:
# [Timestamp] [Verb Phrase Title]
Date: YYYY-MM-DD
Status: Proposed | Accepted | Deprecated | Superseded by [xxx]
Author: [Git username]
## Context
What is the issue we're seeing that motivates this decision?
## Decision
What are we going to do about it?
## Consequences
What becomes easier or more difficult as a result?
## Notes
[Any updates or learnings added after the decision]
Section Purposes¶
Title: Verb phrase describing the decision (e.g., "Adopt Redis for Caching")
Date: When the decision was made or proposed
Status: Current state of the decision - Proposed: Being considered, not yet implemented - Accepted: Approved and implemented/being implemented - Deprecated: No longer relevant but kept for history - Superseded: Replaced by a newer decision (reference it)
Author: Who made or proposed the decision
Context: The situation that led to this decision - Current problems or constraints - Requirements that must be satisfied - Alternatives that were considered - Stakeholders involved
Decision: The choice being made - What technology/approach/pattern - Key implementation details affecting architecture - Scope and boundaries
Consequences: What changes as a result - Positive: Benefits gained, problems solved - Negative: Tradeoffs accepted, increased complexity - Neutral: Other changes, new considerations
Notes: Living section for updates - Implementation learnings - Real-world results - Links to related decisions - Changes in context
File Naming Convention¶
We use a hybrid approach combining timestamps with descriptive verb phrases:
Format: YYYYMMDD_HHMMSS_verb-phrase.md
Examples:
- 20250904_143022_adopt-redis-caching.md
- 20250904_151245_switch-to-htmx.md
- 20250905_093011_implement-tenant-isolation.md
Benefits of this format:
graph LR
A[Timestamp Prefix] --> B[Chronological Sorting]
A --> C[No Collision Issues]
A --> D[Easy to Track Evolution]
E[Verb Phrase Suffix] --> F[Human Readable]
E --> G[Searchable]
E --> H[Descriptive]
style A fill:#e1f5ff
style E fill:#e8f5e9
Timestamp benefits: - Files sort chronologically automatically - No naming conflicts (precise to the second) - Can trace decision timeline
Verb phrase benefits: - Meaningful names in search results - Clear purpose when browsing directory - Easy to reference in discussions
When to Create an ADR¶
Create an ADR When:¶
Technology decisions: - Choosing frameworks (Django over Flask) - Selecting databases (MySQL over PostgreSQL) - Picking deployment platforms (ECS over Kubernetes)
Architecture patterns: - API design approach (REST over GraphQL) - Authentication strategy (Auth0 vs. self-hosted) - Multi-tenancy implementation (separate databases vs. shared)
Integration approaches: - External service integration patterns - Data synchronization strategies - Event-driven vs. request/response
Security and compliance: - Authentication/authorization approach - Data encryption strategy - Audit logging requirements
Development practices: - Testing strategy changes - CI/CD pipeline design - Deployment workflow
Cross-cutting concerns: - Logging and monitoring approach - Error handling strategy - Caching architecture
Don't Create an ADR For:¶
Implementation details: Already covered by code comments
# ❌ Don't create ADR for this
# ADR: "Use List Comprehension for Filtering Users"
# ✅ Just use a code comment
# Filter active users using list comprehension for readability
active_users = [u for u in users if u.is_active]
Temporary workarounds: Short-term fixes that will be replaced
# ❌ Don't create ADR for temporary fix
# ADR: "Workaround for Third-Party API Bug"
# ✅ Just add a TODO comment
# TODO: Remove this workaround when API v2 is released
if api_version == "v1":
result = workaround_function()
Decisions already documented: Covered by standards, policies, or external docs
Minor configuration changes: Not architecturally significant
Reversible decisions: Low-impact changes that can be easily changed
Decision Significance Test¶
Ask these questions to determine if an ADR is warranted:
- Does this decision affect system architecture? (Yes → ADR)
- Will future developers need to understand why we made this choice? (Yes → ADR)
- Does this have long-term implications or affect multiple teams? (Yes → ADR)
- Were there multiple viable options with different tradeoffs? (Yes → ADR)
- Does this involve external dependencies, technologies, or vendors? (Yes → ADR)
If you answered "yes" to 2+ questions, create an ADR.
Creating ADRs¶
Method 1: Management Command (Recommended)¶
For Django projects, use the management command:
# Create new ADR
python manage.py create_decision "adopt-redis-caching"
# Output:
# Created decision record: documentation/decisions/20250904_143022_adopt-redis-caching.md
# Edit the file to add context, decision, and consequences.
The command automatically: - Generates timestamped filename - Fills in date and author from git config - Creates file from template - Shows path for editing
Method 2: Manual Creation¶
If you don't have the management command:
# Copy template
cp documentation/decisions/template.md \
documentation/decisions/20250904_143022_adopt-redis-caching.md
# Edit the file
vim documentation/decisions/20250904_143022_adopt-redis-caching.md
Directory Structure¶
project/
├── documentation/
│ └── decisions/
│ ├── README.md # Process documentation
│ ├── template.md # ADR template
│ ├── 20250904_143022_adopt-redis-caching.md
│ ├── 20250904_151245_switch-to-htmx.md
│ └── 20250905_093011_implement-tenant-isolation.md
ADR Lifecycle¶
Statuses and Transitions¶
stateDiagram-v2
[*] --> Proposed: Create ADR
Proposed --> Accepted: Team approves and implements
Proposed --> [*]: Rejected (delete ADR)
Accepted --> Deprecated: No longer relevant
Accepted --> Superseded: Replaced by new decision
Deprecated --> [*]: Archived
Superseded --> [*]: Archived
note right of Accepted
Most ADRs remain
in this state
end note
Proposed: Decision being considered - Still evaluating options - Gathering feedback - Not yet implemented
Accepted: Decision approved and implemented - Team consensus reached - Implementation started or completed - This is the steady state for most ADRs
Deprecated: Decision no longer relevant - Technology no longer used - Requirements changed - Kept for historical context
Superseded: Replaced by newer decision - Link to the ADR that replaces this one - Explains why the decision changed - Maintains the decision chain
Living Documents¶
Unlike strict immutability, we treat ADRs as "living documents":
## Notes
**2025-09-04**: Initial decision to adopt Redis for session storage.
**2025-09-15**: Implementation completed. Redis performing well with
average response time of 2ms. Session data now persists across
application restarts as expected.
**2025-10-01**: Identified issue with Redis memory usage during high
traffic. Added memory limit configuration and eviction policy. See
configuration in `settings/production.py`.
**2025-11-12**: Expanded Redis usage to include API rate limiting.
Performance remains good. Consider creating separate ADR for rate
limiting pattern.
Notes section captures: - Implementation learnings - Real-world performance results - Configuration changes - Related decisions - Unexpected challenges - Success metrics
Always include timestamps for updates to maintain history.
Example ADRs¶
Example 1: Technology Choice¶
# 20250904_143022 Adopt Redis for Session Storage
Date: 2025-09-04
Status: Accepted
Author: Bob Florian
## Context
Our Django application runs on AWS ECS Fargate with auto-scaling. Currently,
sessions are stored using Django's default database-backed sessions. This
creates several problems:
1. **Stateful processes**: Sessions tie users to specific containers, requiring
sticky sessions at the load balancer
2. **Database load**: Every request hits the database for session data
3. **Scaling friction**: Adding containers requires load balancer reconfiguration
4. **Performance**: Database queries add 50-100ms to every request
We evaluated three alternatives:
- **Database-backed sessions** (current): Simple but slow, not horizontally scalable
- **Redis sessions**: Fast, horizontally scalable, requires infrastructure
- **Signed cookie sessions**: No infrastructure, but limited size and security concerns
Requirements:
- Support horizontal scaling without sticky sessions
- Sub-10ms session access time
- Session persistence across application restarts
- Secure session storage (not client-side)
## Decision
We will adopt Redis for session storage using Amazon ElastiCache.
**Implementation approach**:
- Use `django-redis` package for Django integration
- Configure ElastiCache Redis cluster with replication
- Store all session data in Redis, not database
- Use Redis for other caching needs (query cache, rate limiting)
**Configuration**:
```python
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://elasticache-endpoint:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
}
}
}
Consequences¶
Positive Consequences¶
- True stateless processes: Any container can handle any request
- Better performance: Redis access <5ms vs. 50-100ms for database
- Horizontal scalability: Add containers without configuration changes
- Auto-scaling: ECS can scale based on load without session concerns
- Unified caching: Same infrastructure for sessions and query cache
- Session persistence: Survives application restarts
Negative Consequences¶
- Additional infrastructure: ElastiCache adds cost (~$50/month for small cluster)
- New failure mode: Redis outage affects all users (mitigated with replication)
- Operational complexity: Another service to monitor and maintain
- Session size limits: Redis has memory constraints (mitigated with TTL)
Neutral Consequences¶
- Learning curve: Team needs to understand Redis operations
- Monitoring: Need CloudWatch alarms for Redis metrics
- Backup strategy: Session data is ephemeral, no backup needed
Notes¶
2025-09-04: Initial implementation completed. ElastiCache cluster configured with two nodes (primary + replica) in different AZs for high availability.
2025-09-15: Performance testing shows average session access time of 2ms, down from 75ms with database-backed sessions. Page load times improved by 15%.
2025-10-01: Added CloudWatch alarms for Redis memory usage, CPU, and eviction rate. Configured maxmemory-policy to "volatile-lru" to handle memory pressure.
2025-11-01: Expanded Redis usage to include API rate limiting (django-ratelimit). Sharing infrastructure has been cost-effective. No performance degradation observed.
### Example 2: Architecture Pattern
```markdown
# 20250904_182829 Implement Multi-Tenant Account Authorization
Date: 2025-09-04
Status: Accepted
Author: Bob Florian
## Context
Our SaaS application serves multiple client organizations (tenants). Each tenant
has isolated data, and users belong to a single tenant. We need to ensure:
1. **Data isolation**: Users can only access their tenant's data
2. **Authorization**: Role-based access control within each tenant
3. **Performance**: Authorization checks don't slow down requests
4. **Maintainability**: Pattern is easy to apply across all views
Current situation:
- Basic authentication (login) works
- No systematic tenant isolation
- Manual checks in views (inconsistent, error-prone)
- Some endpoints accidentally leak data across tenants
We evaluated these approaches:
- **Manual checks in each view**: Simple but error-prone, easy to forget
- **Database-level isolation**: Separate databases per tenant (complex, costly)
- **Middleware-based**: Automatic but less flexible for complex rules
- **Mixin-based**: Reusable, explicit, works with Django patterns
## Decision
We will implement multi-tenant authorization using a mixin pattern with
rest_access_policy for fine-grained permissions.
**Pattern components**:
1. **AccountValidationMixin**: Base mixin for tenant-scoped querysets
2. **AccessPolicy classes**: Define role-based permissions per resource
3. **Tenant context**: Stored in request object by middleware
**Implementation**:
```python
class AccountValidationMixin:
"""Mixin to ensure tenant isolation in views."""
def get_account(self, request):
"""Extract tenant from authenticated user."""
if not request.user.is_authenticated:
raise PermissionDenied("Authentication required")
return request.user.tenant
def get_queryset(self):
"""Filter queryset by tenant."""
account = self.get_account(self.request)
return super().get_queryset().filter(tenant=account)
class DocumentAccessPolicy(AccessPolicy):
"""Define who can access documents."""
statements = [
{
"action": ["list", "retrieve"],
"principal": "authenticated",
"effect": "allow",
"condition": "is_tenant_member"
},
{
"action": ["create", "update", "destroy"],
"principal": "authenticated",
"effect": "allow",
"condition": "is_owner_or_admin"
}
]
class DocumentViewSet(AccountValidationMixin, viewsets.ModelViewSet):
"""Document API with tenant isolation."""
permission_classes = [IsAuthenticated, DocumentAccessPolicy]
# Queryset automatically filtered by tenant via mixin
Consequences¶
Positive Consequences¶
- Systematic isolation: Every view using the mixin is tenant-scoped
- Explicit pattern: Clear, visible authorization in every viewset
- Flexible permissions: AccessPolicy supports complex role-based rules
- Audit trail: Permission checks are logged
- DRY: Reusable mixin reduces code duplication
- Testable: Easy to test tenant isolation in unit tests
Negative Consequences¶
- Must remember to use mixin: Developers can forget (mitigated with linting)
- Performance overhead: Extra query filter on every request (minimal)
- Learning curve: Team needs to understand AccessPolicy syntax
- Manual in some cases: Complex authorization still requires custom logic
Neutral Consequences¶
- Consistent pattern: All viewsets follow the same structure
- Documentation needed: Need to document pattern for new developers
- Migration effort: Existing views need to be updated to use mixin
Notes¶
2025-09-04: Initial implementation with AccountValidationMixin and basic AccessPolicy classes for core resources (users, documents, projects).
2025-09-20: Added custom management command to audit all viewsets and verify they use AccountValidationMixin. Found and fixed 3 viewsets that were missing tenant isolation.
2025-10-15: Extended pattern to handle multi-database routing. Some tenants
have separate databases. Mixin now supports using() based on tenant.
2025-12-01: Team is comfortable with pattern. New viewsets consistently use mixin. No tenant data leakage incidents since implementation.
Related ADRs:
- 20250904_170657_adopt-multi-database-architecture.md - Database routing
- 20250904_190206_adopt-django-rest-framework-api-design.md - API patterns
## ADR Management
### Finding ADRs
**By chronological order**:
```bash
# List all ADRs in order
ls documentation/decisions/*.md
# Recent decisions
ls -lt documentation/decisions/*.md | head -5
By topic:
# Search filenames
ls documentation/decisions/*redis*.md
# Search content
grep -r "Redis" documentation/decisions/
By status:
# Find all accepted decisions
grep -l "Status: Accepted" documentation/decisions/*.md
# Find superseded decisions
grep -l "Status: Superseded" documentation/decisions/*.md
Updating ADRs¶
Add implementation notes:
## Notes
**2025-11-01**: Implementation completed. Real-world performance exceeds
expectations. Average response time 2ms vs. estimated 5ms.
Supersede an ADR:
# 20250904_143022 Adopt Redis for Session Storage
Status: Superseded by 20251205_101530_migrate-to-dynamodb-sessions.md
Create the new ADR explaining why:
# 20251205_101530 Migrate to DynamoDB for Session Storage
## Context
We originally adopted Redis for session storage (see
20250904_143022_adopt-redis-caching.md). After 1 year of operation,
we've identified limitations:
1. Redis requires manual management of ElastiCache clusters
2. Cost has grown from $50/month to $200/month
3. We already use DynamoDB for other features
4. DynamoDB's auto-scaling better matches our traffic patterns
## Decision
Migrate session storage from Redis to DynamoDB...
Team Notifications¶
Set up notifications for new ADRs:
# .git/hooks/post-merge
#!/bin/bash
# Notify team of new ADRs after git pull
NEW_ADRS=$(git diff HEAD@{1} --name-only --diff-filter=A | grep "documentation/decisions/.*\.md")
if [ -n "$NEW_ADRS" ]; then
echo "🏛️ New Architecture Decision Records:"
echo "$NEW_ADRS" | while read adr; do
TITLE=$(grep "^# " "$adr" | head -1 | sed 's/^# //')
CONTEXT=$(grep -A 2 "## Context" "$adr" | tail -1)
echo " → $adr"
echo " \"$CONTEXT...\""
done
fi
Team Process¶
Who Can Create ADRs?¶
Anyone who understands the decision and can document it: - Developers implementing the feature - Architects designing the system - Team leads making technology choices - Product managers defining requirements
Best practice: The person who researched the options and made the recommendation should write the ADR.
Review and Approval¶
Lightweight process:
- Create ADR with status "Proposed"
- Share in PR for visibility
- Discuss in code review or team meeting
- Update based on feedback
- Merge when team agrees
- Change status to "Accepted" when implemented
For major decisions: - Schedule dedicated discussion time - Include relevant stakeholders - Document dissenting opinions in Notes - Allow time for consideration (don't rush)
Governance Questions¶
Discuss these with your team before starting:
- Who can create ADRs? Anyone? Specific roles?
- What requires team approval? All ADRs? Only major ones?
- How do we handle disagreements? Consensus? Majority? Tech lead decides?
- What's our decision-making principle? Disagree and commit? Unanimous consent?
Best Practices¶
Writing Good ADRs¶
Be specific:
# ❌ Vague
We will use a database for data storage.
# ✅ Specific
We will use MySQL 8.0 for our primary relational data store, hosted on
AWS RDS with Multi-AZ deployment.
Explain tradeoffs:
## Consequences
### Positive
- Fast read performance (indexed queries <10ms)
- Familiar to team (everyone knows SQL)
### Negative
- Scaling writes requires sharding (complex)
- Schema migrations can be slow on large tables
### Neutral
- Need to learn MySQL-specific features (vs. PostgreSQL)
Include context:
## Context
We currently handle 100K requests/day with 1M database records. Usage
is growing 20% monthly. Current PostgreSQL setup is maxing out at 80%
CPU during peak hours (2-4 PM EST).
We need to support:
- 500K requests/day within 6 months
- <100ms p95 response times
- $500/month infrastructure budget
Be honest about uncertainty:
## Notes
**2025-09-04**: We're estimating Redis will handle our scale, but we
haven't tested beyond 10K concurrent sessions. Will monitor closely
and may need to revisit if we exceed 50K sessions.
Common Mistakes¶
Too abstract:
# ❌ Not actionable
We will follow best practices for API design.
# ✅ Concrete
We will design REST APIs using Django REST Framework with:
- snake_case field names
- Pagination on all list endpoints
- Filtering via django-filter
Missing consequences:
# ❌ Incomplete
## Consequences
This will make our API faster.
# ✅ Complete
## Consequences
### Positive
- Redis caching reduces API response time from 200ms to 50ms
- Lower database load (60% fewer queries)
### Negative
- ElastiCache adds $50/month cost
- Redis outage affects all users
- Need to monitor cache hit rate
### Neutral
- Team needs to learn Redis basics
- Cache invalidation strategy required
Too much detail:
# ❌ Too detailed (belongs in code)
We will use Redis with the following configuration:
- maxmemory: 2gb
- maxmemory-policy: allkeys-lru
- timeout: 300
- tcp-keepalive: 60
[50 more configuration parameters...]
# ✅ Right level (architecture-focused)
We will use Redis for session storage with memory eviction policy
set to LRU. Configuration details are in settings/production.py.
Integration with Development Workflow¶
During Planning¶
Create ADRs before implementing major features:
graph LR
A[Feature Request] --> B[Research Options]
B --> C[Create ADR]
C --> D[Team Review]
D --> E{Approved?}
E -->|Yes| F[Implement]
E -->|No| B
F --> G[Update ADR Notes]
style C fill:#e1f5ff
style D fill:#fff9e1
style F fill:#e8f5e9
During Development¶
Update ADRs as you learn:
## Notes
**2025-09-04**: Started implementation.
**2025-09-10**: Discovered Redis Cluster doesn't support all commands
we need. Switching to single-node with replication instead.
**2025-09-15**: Implementation complete. Performance better than expected.
During Code Review¶
Reference ADRs in PRs:
## Summary
Implements Redis session storage as decided in
documentation/decisions/20250904_143022_adopt-redis-caching.md
## Changes
- Added django-redis dependency
- Configured ElastiCache connection
- Updated session settings
- Added Redis health check
## Testing
- Session persistence across restarts: ✅
- Performance: 2ms average (better than 5ms target)
During Onboarding¶
New team members read ADRs to understand:
Suggested reading order:
- Framework decisions: Why Django? Why DRF?
- Infrastructure decisions: Why AWS? Why ECS?
- Architecture patterns: Multi-tenancy, database routing
- Recent decisions: What changed in the last 6 months?
Tools and Automation¶
Management Command¶
Create a Django management command for ADRs:
# management/commands/create_decision.py
from django.core.management.base import BaseCommand
from datetime import datetime
import os
import subprocess
class Command(BaseCommand):
help = "Create a new Architecture Decision Record"
def add_arguments(self, parser):
parser.add_argument("title", type=str, help="Decision title (verb-phrase)")
def handle(self, *args, **options):
title = options["title"]
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{timestamp}_{title}.md"
filepath = f"documentation/decisions/{filename}"
# Get git author
try:
author = subprocess.check_output(
["git", "config", "user.name"],
text=True
).strip()
except subprocess.CalledProcessError:
author = "Unknown"
# Create from template
with open("documentation/decisions/template.md", "r") as template:
content = template.read()
content = content.replace("YYYY-MM-DD", datetime.now().strftime("%Y-%m-%d"))
content = content.replace("[Git username]", author)
content = content.replace("[Verb Phrase Title]", title.replace("-", " ").title())
with open(filepath, "w") as f:
f.write(content)
self.stdout.write(
self.style.SUCCESS(f"Created decision record: {filepath}")
)
self.stdout.write("Edit the file to add context, decision, and consequences.")
Linting¶
Validate ADR format:
# scripts/validate_adrs.py
import re
import sys
from pathlib import Path
def validate_adr(filepath):
"""Validate ADR has required sections."""
with open(filepath) as f:
content = f.read()
required_sections = ["## Context", "## Decision", "## Consequences"]
missing = [s for s in required_sections if s not in content]
if missing:
print(f"❌ {filepath}: Missing sections: {missing}")
return False
# Check status is valid
status_match = re.search(r"Status: (\w+)", content)
if not status_match:
print(f"❌ {filepath}: Missing Status field")
return False
valid_statuses = ["Proposed", "Accepted", "Deprecated", "Superseded"]
status = status_match.group(1)
if status not in valid_statuses:
print(f"❌ {filepath}: Invalid status '{status}'")
return False
print(f"✅ {filepath}")
return True
if __name__ == "__main__":
adrs = Path("documentation/decisions").glob("*.md")
adrs = [a for a in adrs if a.name not in ["README.md", "template.md"]]
results = [validate_adr(adr) for adr in adrs]
if not all(results):
sys.exit(1)
Run in CI:
Next Steps¶
- Set up ADR directory: Create
documentation/decisions/with README and template - Create management command: Automate ADR creation
- Document your first decision: Why you're adopting ADRs (meta!)
- Identify recent decisions: Retroactively document major choices
- Integrate into workflow: Make ADRs part of planning process
- Train the team: Share this guide, create example ADRs
- Iterate: Adjust process based on team feedback
Start Simple
Don't create ADRs for every decision from the past. Start with new decisions and retroactively document 2-3 major choices. The value comes from building the habit, not documenting ancient history.
Further Reading¶
- ADR GitHub - Community resources and tools
- Michael Nygard's Original Post - The foundation of lightweight ADRs
- MADR Project - More sophisticated templates
- ADR Tools - Command-line tools for managing ADRs
- Zenith ADR Examples - Real-world ADRs from production system
Example: Adopting ADRs¶
Here's an example ADR about adopting ADRs (meta!):
# 20250904_155954 Adopt Architecture Decision Records
Date: 2025-09-04
Status: Accepted
Author: Bob Florian
## Context
The project has grown in complexity with multiple integrations, technology
choices (HTMX, Alpine.js, TailwindCSS), and database patterns (multi-tenancy,
multiple databases). As the team scales and new developers join, we need a way to:
- Preserve reasoning behind architectural choices
- Ensure team alignment on important decisions
- Help new team members understand system evolution
- Create searchable history of decisions
- Reduce repeated architectural discussions
Currently, decisions are communicated through:
- Code comments (limited context)
- Slack conversations (ephemeral)
- Meeting notes (scattered across different systems)
This creates knowledge silos and makes it difficult to understand the "why"
behind architectural choices.
## Decision
We will adopt Architecture Decision Records (ADRs) using Michael Nygard's
lightweight template.
**Implementation**:
- Directory: `/documentation/decisions/`
- Naming: `YYYYMMDD_HHMMSS_verb-phrase.md` format
- Template: Context, Decision, Consequences, Notes sections
- Tooling: Django management command `create_decision`
- Living documents: Updates in Notes section with timestamps
**Scope**: Document architectural decisions that affect system design,
technology choices, integration patterns, and cross-team coordination.
## Consequences
### Positive Consequences
- Knowledge preservation for future developers
- Team alignment on important decisions
- Easier onboarding for new team members
- Searchable decision history
- Reduced repetition of architectural discussions
### Negative Consequences
- Additional process to remember
- Maintenance overhead for keeping ADRs updated
- Learning curve for ADR format
- Risk of over-documentation
### Neutral Consequences
- Need to maintain decisions directory
- May need to adjust template based on experience
- Integration with development workflow
## Notes
**2025-09-04**: Initial implementation completed including directory structure,
README, template, management command, and this first ADR as an example.
**2025-10-01**: Team has created 15 ADRs. Process is lightweight and valuable.
New developers appreciate the context. Continuing to refine.