Skip to main content

Architecture Decision Record (ADR) Template

October 10, 2025By CTO21 min read
...
templates

A comprehensive template for documenting architectural decisions with context, alternatives considered, and consequences.

Template Type:Documentation

Architecture Decision Record (ADR) Template

Architecture Decision Records (ADRs) help teams document important architectural decisions, the context in which they were made, and the consequences. This template provides a structured approach to creating clear, useful ADRs.

Why Use ADRs?

Benefits:

  • Creates institutional memory
  • Helps new team members understand "why"
  • Prevents rehashing old decisions
  • Shows evolution of architecture
  • Facilitates better decision-making

When to write an ADR:

  • Significant architectural changes
  • Technology choices
  • Major refactorings
  • Performance optimizations
  • Security decisions
  • Scalability approaches

The Template

markdown
# ADR-[NUMBER]: [Title]

## Status

[Proposed | Accepted | Deprecated | Superseded by ADR-XXX]

## Context

[Describe the problem or opportunity that requires a decision. Include:
- Current state of the system
- Business requirements driving the decision
- Technical constraints or requirements
- Timeline considerations
- Team capabilities
- Budget constraints]

## Decision

[State the decision clearly and concisely. Be specific about what was chosen.]

## Alternatives Considered

### Alternative 1: [Name]

**Description:**
[Explain the alternative]

**Pros:**
- [Advantage 1]
- [Advantage 2]

**Cons:**
- [Disadvantage 1]
- [Disadvantage 2]

**Why not chosen:**
[Explain reasoning]

### Alternative 2: [Name]

[Repeat structure above]

## Consequences

### Positive

- [Positive outcome 1]
- [Positive outcome 2]

### Negative

- [Negative consequence 1]
- [Negative consequence 2]

### Risks

- [Risk 1 and mitigation plan]
- [Risk 2 and mitigation plan]

## Implementation Notes

[Technical details about how the decision will be implemented:
- Migration plan if applicable
- Timeline
- Rollback strategy
- Testing approach]

## References

- [Link to related design docs]
- [Link to research/benchmarks]
- [Link to related ADRs]

## Metadata

- **Author:** [Name]
- **Date:** [YYYY-MM-DD]
- **Reviewers:** [Names]
- **Stakeholders:** [Teams/individuals affected]

Complete Example

markdown
# ADR-023: Migrate from REST to GraphQL for Mobile API

## Status

Accepted (2025-10-01)

## Context

Our mobile application currently uses a REST API with 47 different endpoints. We're experiencing several challenges:

**Performance Issues:**
- Mobile app makes 8-12 API calls to render the home screen
- Over-fetching: Each endpoint returns more data than needed
- Under-fetching: Often need multiple requests to get related data
- 3G users experience poor loading times

**Developer Experience:**
- Frontend team blocked waiting for new endpoints
- Backend team spending 60% of time on mobile API endpoints
- Version management becoming complex (v1, v2, v3 endpoints)
- Documentation constantly out of date

**Business Requirements:**
- Need to launch new features faster
- Support multiple mobile platforms (iOS, Android)
- Reduce mobile data usage
- Improve app performance metrics

**Team:**
- 3 backend engineers familiar with Node.js/TypeScript
- 2 mobile engineers (React Native)
- All have GraphQL experience from previous roles

**Timeline:**
- Must show improvement within 1 quarter
- Can accept gradual migration approach

**Budget:**
- No additional infrastructure budget approved
- Must work within existing AWS spend

## Decision

We will adopt GraphQL for our mobile API while maintaining REST for web and third-party integrations.

Specifically:
- Implement GraphQL server using Apollo Server
- Use schema-first approach
- Migrate mobile endpoints gradually over 3 months
- Keep existing REST API for web and partners
- Use DataLoader for batching and caching

## Alternatives Considered

### Alternative 1: Optimize Existing REST API

**Description:**
Improve existing REST endpoints with better caching, response shaping, and field selection parameters.

**Example:**

GET /api/users/123?fields=id,name,avatar&include=posts.comments


**Pros:**
- No new technology to learn
- Lower implementation risk
- Existing tooling and monitoring
- No client-side changes needed

**Cons:**
- Doesn't solve over-fetching completely
- Still requires multiple round trips
- Query language becomes complex
- Caching strategy complicated
- Doesn't improve developer velocity significantly

**Why not chosen:**
This is a band-aid solution. We've already implemented partial optimizations and still face the same fundamental issues. Would require significant effort without addressing root causes.

### Alternative 2: Backend for Frontend (BFF)

**Description:**
Create mobile-specific aggregation layer that combines REST endpoints.

Mobile App → Mobile BFF → Existing REST APIs


**Pros:**
- Solves over-fetching and multiple requests
- No changes to existing REST APIs
- Mobile team has full control
- Can optimize for mobile use cases

**Cons:**
- Additional service to maintain
- Duplicates logic from other services
- Still doesn't give clients query flexibility
- Increases infrastructure complexity
- Doesn't improve developer velocity much

**Why not chosen:**
Adds complexity without solving the core issue of inflexible data fetching. Teams would still need backend changes for new features.

### Alternative 3: gRPC

**Description:**
Use gRPC for mobile-backend communication.

**Pros:**
- Excellent performance
- Strong typing with Protocol Buffers
- Bidirectional streaming
- HTTP/2 benefits

**Cons:**
- Poor browser support (web can't use it)
- More complex than GraphQL for querying
- Steeper learning curve
- Less flexible querying
- Tooling less mature for mobile

**Why not chosen:**
While performant, doesn't solve our core problem of flexible data fetching. Better suited for internal service-to-service communication than client-facing APIs.

## Consequences

### Positive

**Performance:**
- Reduced API calls from 8-12 to typically 1-2
- Mobile data usage down ~40% (based on similar migrations)
- Improved loading times, especially on slow networks

**Developer Velocity:**
- Mobile team can iterate without backend changes for UI variations
- Reduced coordination overhead between frontend and backend
- Self-documenting API through introspection

**Codebase:**
- Single source of truth for API schema
- Type safety from GraphQL to TypeScript (code generation)
- Better API documentation automatically

### Negative

**Complexity:**
- New technology adds to stack diversity
- More complex error handling (partial errors)
- Caching strategy differs from REST (can't use HTTP cache headers)
- Monitoring and debugging requires new approaches

**Performance Risks:**
- N+1 query problem if not careful with DataLoader
- Complex queries could be expensive
- Need query complexity analysis and limits

**Learning Curve:**
- Team needs to learn GraphQL best practices
- Schema design requires different thinking
- Mobile team needs to learn query optimization

### Risks

**Risk: Performance degradation from N+1 queries**
- Mitigation: Implement DataLoader from day 1
- Mitigation: Add query complexity analysis
- Mitigation: Monitor query performance with Apollo Studio

**Risk: Overly complex queries from clients**
- Mitigation: Implement query depth limiting (max depth: 5)
- Mitigation: Query cost analysis and limits
- Mitigation: Rate limiting per query complexity

**Risk: Breaking changes during migration**
- Mitigation: Support both REST and GraphQL during transition
- Mitigation: Gradual migration screen by screen
- Mitigation: Comprehensive integration tests

**Risk: Team resistance or difficulty adopting**
- Mitigation: Dedicated training sessions (2 weeks)
- Mitigation: Start with simple endpoints
- Mitigation: Pair programming for first implementations

## Implementation Notes

### Phase 1: Foundation (Weeks 1-2)

```typescript
// Setup Apollo Server
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

const typeDefs = `#graphql
  type User {
    id: ID!
    name: String!
    email: String!
  }

  type Query {
    user(id: ID!): User
  }
`;

const resolvers = {
  Query: {
    user: async (_, { id }, context) => {
      return context.dataSources.users.getUser(id);
    },
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
});

Phase 2: DataLoader Integration (Week 2-3)

typescript
import DataLoader from 'dataloader';

class UserAPI {
  constructor() {
    this.userLoader = new DataLoader(async (ids) => {
      const users = await db.users.findAll({
        where: { id: { in: ids } }
      });

      // Return in same order as requested
      return ids.map(id => users.find(u => u.id === id));
    });
  }

  getUser(id) {
    return this.userLoader.load(id);
  }
}

Phase 3: Migration Strategy (Weeks 4-12)

Week 4-6: High-impact screens

  • Home feed
  • User profile
  • Post detail

Week 7-9: Secondary screens

  • Settings
  • Notifications
  • Search

Week 10-12: Remaining screens

  • Onboarding
  • Edge cases
  • Admin features

Rollback Strategy

If we need to rollback:

  1. Feature flag controls GraphQL vs REST
  2. Both implementations maintained during migration
  3. Can switch back to REST per-screen if needed
  4. Full rollback possible within 1 day

Monitoring

typescript
// Apollo Server with monitoring
const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [
    ApolloServerPluginLandingPageGraphQLPlayground(),
    {
      async requestDidStart() {
        return {
          async willSendResponse({ response, metrics }) {
            // Log query performance
            logger.info('graphql_query', {
              duration: metrics.duration,
              query: metrics.queryPlanTrace,
              errors: response.errors
            });
          }
        };
      }
    }
  ]
});

Success Metrics

Performance:

  • Reduce average API calls per screen from 10 to <3
  • Reduce mobile data usage by 30%+
  • Improve P95 screen load time by 40%

Developer Velocity:

  • Reduce feature development time by 25%
  • Reduce backend dependency for mobile features
  • 100% schema test coverage

Quality:

  • Zero data privacy incidents
  • Maintain 99.9% uptime
  • <1% error rate

Testing Approach

typescript
// Schema testing
import { makeExecutableSchema } from '@graphql-tools/schema';

describe('GraphQL Schema', () => {
  it('should fetch user with posts', async () => {
    const query = `
      query GetUser($id: ID!) {
        user(id: $id) {
          id
          name
          posts {
            id
            title
          }
        }
      }
    `;

    const result = await graphql({
      schema,
      source: query,
      variableValues: { id: '123' }
    });

    expect(result.data.user.name).toBe('Test User');
    expect(result.data.user.posts).toHaveLength(5);
  });
});

References

Metadata

  • Author: Sarah Chen (CTO)
  • Date: 2025-10-01
  • Reviewers:
    • Mike Johnson (Lead Backend Engineer)
    • Lisa Wang (Lead Mobile Engineer)
    • David Kim (Engineering Manager)
  • Stakeholders:
    • Mobile team (2 engineers)
    • Backend team (3 engineers)
    • Product team
  • Approved by: Engineering Leadership Team

## ADR Management Best Practices

### 1. Numbering

Use sequential numbers (ADR-001, ADR-002, etc.) or date-based (ADR-20251001).

### 2. Storage

docs/ adr/ 0001-use-postgres.md 0002-adopt-microservices.md 0023-migrate-to-graphql.md README.md # Index of all ADRs


### 3. Review Process

```markdown
1. Author creates ADR in "Proposed" status
2. Share with stakeholders for review
3. Discuss in architecture review meeting
4. Update based on feedback
5. Mark as "Accepted" when approved
6. Implement decision
7. Update status if deprecated

4. Maintenance

  • Review ADRs quarterly
  • Update status if superseded
  • Link to new ADRs when decisions change
  • Don't delete old ADRs (they're history)

5. Discovery

Create an index:

markdown
# Architecture Decision Records

## Active

- [ADR-023: Migrate to GraphQL](0023-migrate-to-graphql.md) - 2025-10-01
- [ADR-019: Redis Caching](0019-redis-caching.md) - 2025-08-15

## Superseded

- [ADR-010: Use MongoDB](0010-use-mongodb.md) - Superseded by ADR-015

Tools

ADR CLI

bash
# Install
npm install -g adr-tools

# Initialize
adr init docs/adr

# Create new ADR
adr new "Migrate to GraphQL"

# Supersede old ADR
adr new -s 10 "Use PostgreSQL instead of MongoDB"

Templates Repository

Keep templates in your repository:

.github/
  ADR_TEMPLATE.md
  PULL_REQUEST_TEMPLATE.md
docs/
  templates/
    adr-template.md
    rfc-template.md

Common Mistakes to Avoid

1. Too Much Detail

Bad: 500 lines explaining every implementation detail Good: Focus on the "why" and "what", link to implementation docs

2. Too Little Context

Bad: "We chose React because it's better" Good: Explain the specific problems React solves for your team

3. Not Considering Alternatives

Bad: Only documenting the chosen solution Good: Show alternatives considered and why they weren't chosen

4. Writing After the Fact

Bad: Creating ADRs months after the decision Good: Write ADR before or during decision-making

5. Never Updating Status

Bad: ADRs from 2020 still showing as "Active" Good: Mark deprecated/superseded as architecture evolves

Integration with Process

In Pull Requests

markdown
## Changes
- Implement GraphQL endpoint for users

## ADR
- Related to ADR-023: Migrate to GraphQL
- Implements Phase 1 of migration plan

In Design Docs

markdown
## References
- ADR-023: GraphQL Migration
- ADR-019: Caching Strategy

In Onboarding

New engineers should read:

  1. ADR index
  2. All Active ADRs
  3. Recent Superseded ADRs (for context)

ADRs are living documents. They capture not just decisions, but the thinking behind them. This institutional knowledge becomes invaluable as teams and systems evolve.

Want more insights like this?

Join thousands of CTOs and technical leaders getting weekly insights on leadership and system design.

No spam. Unsubscribe anytime.