Skip to content
M03Technical choices and abstractionsLesson 1 of 4

Explicit trade-offs and ADRs

Every technical decision trades one cost for another. How to judge decisions by reversibility and record the why in a short ADR.

Goal
Judge a technical decision by what it costs and how reversible it is, and record the reasoning in an ADR someone can understand a year from now.

Six months after launch, a new developer asks why Linkr uses its own Postgres as a job queue instead of SQS or RabbitMQ. Nobody remembers. Maybe it was laziness, maybe it was a great call. Without knowing the reason, the team hesitates: changing it might break something nobody understands, and keeping it might mean carrying a mistake.

An unrecorded decision causes this problem even when it was a good one: nobody knows why it was made or when it stops applying.

Some common examples, with what you get and what you give up:

Choice You gain You pay
Managed service (RDS, Supabase) Backups, failover and upgrades handled for you Higher cost, less control, lock-in
Microservices Independent deploys and scaling per team Network between services, distributed consistency, operations
Cache in front of the database Fast reads, less database load Stale data, invalidation, one more piece that can fail
ORM Productivity, types, migrations Hidden queries, N+1, less optimized SQL
NoSQL database Flexible schema, easy horizontal scaling Limited joins and transactions, integrity becomes your job

If someone says an option “has no downside”, they don’t understand the option yet. The engineering work is to make the costs explicit and pick the ones your context can afford.

The most useful question: can you undo it?

Section titled “The most useful question: can you undo it?”

Classify each decision by reversibility:

  • Two-way door: easy to undo. A date library, folder structure, lint tool, an email provider behind an interface. Decide quickly with enough information and move on.
  • One-way door: expensive or impossible to undo. Data model, primary database, public ID format, API contracts used by third parties, main language. These are worth the time: prototype, discuss, write an ADR.
quadrantChart
  title How much time to spend on a decision
  x-axis Easy to reverse --> Hard to reverse
  y-axis Low impact --> High impact
  quadrant-1 Investigate and record
  quadrant-2 Decide and measure
  quadrant-3 Decide fast
  quadrant-4 Mind the details
  Date library: [0.15, 0.2]
  Email provider: [0.3, 0.45]
  Caching strategy: [0.35, 0.7]
  Public IDs: [0.8, 0.35]
  Data model: [0.85, 0.85]
  Primary database: [0.9, 0.75]

The most common mistake among people who build fast with AI is to flip this around: spend hours choosing a component library and accept whatever schema the AI generated without a second thought.

An ADR (Architecture Decision Record) is a short file, versioned with the code, that records an important decision. Keep them in docs/adr/ with sequential numbers.

# ADR 0007: Use Postgres as the job queue
- Status: accepted
- Date: 2026-03-12
- Deciders: Ana, Douglas
## Context
We need to process click events and email sends outside the request.
Current volume: ~50k jobs/day, peak of ~20 jobs/s. Team of 2, with no
experience running RabbitMQ or Kafka. We already run Postgres with backups and PITR.
## Decision
Use a jobs table in Postgres with `SELECT ... FOR UPDATE SKIP LOCKED`
(via the pg-boss library) instead of a dedicated broker.
## Alternatives considered
- SQS: reliable, but adds IAM, another bill to track and more awkward local testing.
- Redis + BullMQ: fast, but we'd have to guarantee Redis persistence.
## Consequences
+ Zero new infrastructure; the job is enqueued in the same transaction as the data.
+ Existing backups and monitoring already cover the queue.
- The database takes on extra write load.
- Not suitable for fan-out to many consumers.
## When to revisit
If we go above ~500 jobs/s sustained, or if vacuum on the jobs table
starts affecting the latency of the main queries.

What makes an ADR useful:

  • Context with numbers. “Low volume” says nothing; “50k jobs/day” lets you reassess later.
  • Real alternatives, with the reason each was rejected.
  • Negative consequences. If it only has upsides, it’s incomplete.
  • A trigger to revisit. It’s the most forgotten part and the most valuable one, because it says when the decision stops applying.

ADRs aren’t edited once accepted. If the decision changes, write a new ADR that supersedes the old one and set the old one’s status to “superseded by 0012”. That way the history of the reasoning stays readable.

  • Deciding by fashion. “Netflix uses it” isn’t an argument. Netflix has thousands of engineers and problems you don’t have.
  • An ADR for everything. If you record every library choice, nobody reads any of them. Save them for one-way doors and decisions someone will question.
  • Endless debate over a two-way door. If you can swap it in a day, decide in an hour.
  • What AI tends to generate: the “internet default” choice for everything (Kubernetes, microservices, Kafka), with no regard for the size of your team or your traffic. Always ask the AI for the downsides of each suggestion and the context where it would be wrong.

Go deeper

Materials in English unless noted.