← All posts

Architecture Decision Records: The Full-Stack Engineer's Secret Weapon

Architecture Decision Records: The Full-Stack Engineer's Secret Weapon

I've watched teams waste weeks re-debating the same architectural choices because no one remembered why we chose PostgreSQL over MongoDB, or why we split that monolith into services. Architecture Decision Records (ADRs) solve this, but most teams either don't write them or write them so poorly they become shelfware. After maintaining ADRs across three different full-stack projects, I've learned what actually works.

What Makes ADRs Different from Regular Documentation

ADRs aren't design docs or technical specs. They're lightweight records of why you made a choice at a specific point in time. The key insight: ADRs are immutable. You don't update them when circumstances change—you write a new one that supersedes the old one. This creates a decision history that's invaluable when debugging architecture or onboarding new engineers.

The format is deliberately minimal. I use a modified version of Michael Nygard's template that works well for full-stack decisions:

# ADR-015: Use Redis for Session Storage

## Status
Accepted (supersedes ADR-008)

## Context
Our Node.js API currently stores sessions in-memory, which breaks when we scale horizontally. We're adding a second API instance behind a load balancer next sprint. Session data is ~2KB per user, 10K active sessions at peak.

## Decision
We will use Redis for session storage with a 24-hour TTL.

## Consequences

### Positive
- Horizontal scaling works immediately
- Built-in TTL handles session expiration
- Redis persistence handles restarts gracefully
- 5ms p95 latency is acceptable for our use case

### Negative
- Adds Redis as a new operational dependency
- Session data leaves the application boundary (security consideration)
- Redis failure means all users lose sessions
- Monthly cost: ~$25 for managed Redis

### Neutral
- Need to implement Redis connection pooling
- Monitoring: add Redis metrics to Datadog

## Alternatives Considered

### PostgreSQL sessions table
Rejected: Adds load to primary DB, requires active cleanup jobs

### JWT tokens (stateless)
Rejected: Can't invalidate sessions on logout, token size bloats requests

### Sticky sessions
Rejected: Complicates deployments, uneven load distribution

Where to Store ADRs (and Why It Matters)

Store ADRs in your codebase, not in Confluence or Notion. I keep them in docs/adr/ at the repo root. This has three major benefits: ADRs version with the code, they're reviewable via pull requests, and engineers actually find them because they're in the repo. For monorepos, I structure it like docs/adr/backend/, docs/adr/frontend/, docs/adr/infrastructure/.

Hot take: If your ADR requires more than 30 minutes to write, you're writing a design doc, not an ADR. The value is in capturing decisions quickly while context is fresh, not in creating comprehensive architecture documentation.

Real-World ADR: Choosing a Frontend State Management Solution

Here's an actual ADR I wrote when choosing between Redux, Zustand, and React Query for a React application. Notice how it focuses on our specific constraints, not general pros/cons:

# ADR-023: Use React Query + Zustand for State Management

## Status
Accepted

## Context
Our React dashboard has three types of state:
1. Server cache (API data): User profiles, analytics data, settings
2. UI state: Modal open/closed, selected filters, sidebar collapsed
3. Form state: Handled by React Hook Form (out of scope)

Current pain: Redux boilerplate is slowing feature development. Engineers spend 40% of PR time on action creators and reducers. We have 12 API endpoints with manual cache invalidation logic that's buggy.

## Decision
Use React Query for server state, Zustand for UI state.

## Consequences

### Positive
- React Query handles caching, refetching, invalidation automatically
- Zustand is 10 lines of code vs 50+ for Redux equivalents
- Bundle size: -45KB (Redux + toolkit + saga removed)
- Onboarding: New engineers productive in hours, not days

### Negative
- Team needs to learn two libraries instead of one
- Existing Redux code needs gradual migration
- Less established patterns than Redux (smaller community)

## Implementation Notes

// Zustand store example
import create from 'zustand'

const useUIStore = create((set) => ({
  sidebarOpen: true,
  selectedFilters: [],
  toggleSidebar: () => set((state) => ({ 
    sidebarOpen: !state.sidebarOpen 
  }))
}))

// React Query usage
const { data, isLoading } = useQuery(
  ['user', userId],
  () => fetchUser(userId),
  { staleTime: 5 * 60 * 1000 }
)

When to Write an ADR (and When to Skip It)

Write an ADR when the decision has long-term consequences, affects multiple engineers, or will be questioned later. I write them for: database choices, authentication strategies, deployment architecture, API design patterns, state management approaches, and third-party service selections. Skip ADRs for: coding style preferences (use linters), temporary workarounds, obvious choices with no alternatives, and decisions easily reversed in a day.

  • Triggers for writing an ADR: Someone says 'we should document this decision'
  • You're about to add a new major dependency to package.json
  • The decision impacts multiple teams or services
  • You find yourself explaining the same architectural choice twice
  • The choice has security, performance, or cost implications
  • You're superseding a previous architectural decision

Making ADRs Part of Your Workflow

The best ADR process is invisible. I include ADR creation in our definition of done for architecture-heavy tickets. The PR that implements the change includes the ADR. This means the ADR gets reviewed alongside the code, and they're merged together. No separate documentation phase, no falling behind.

Create a template file at docs/adr/000-template.md and a simple CLI script to generate new ADRs with auto-incrementing numbers. Make it easier to write an ADR than to skip it. I use a 10-line bash script that copies the template and opens it in my editor.

ADRs have saved my teams from architectural amnesia more times than I can count. Six months from now, when someone asks 'why did we choose this?', you'll have an answer that includes the constraints and alternatives you considered. That's the difference between a team that learns from decisions and one that keeps making the same mistakes. Start with your next architectural choice—write it down, make it reviewable, and watch your team's architectural memory improve.