DEV Community

Cover image for 52/60 Days System Design Questions
Joud Awad
Joud Awad

Posted on

52/60 Days System Design Questions

Your API just shipped a breaking change.

/users now returns fullName instead of first_name + last_name. 3 mobile clients broke. 1 partner integration went down. Your on-call is not happy.

You had a versioning strategy. It just wasn't the right one.

There are 4 ways to version an API. Here's what actually happens when you pick each one in production:

A — URL path versioning (/v1/users, /v2/users)
Simple. Explicit. Every request makes the version visible in logs and caches. But now you're maintaining 2 full route trees. A bugfix in the business logic layer has to be patched in both. Teams quietly let v1 rot.

B — Header versioning (API-Version: 2)
Clean URLs. Version negotiation in the transport layer, not the path. Harder to test in a browser, invisible in logs unless you instrument for it, and clients forget to send the header — defaulting to whatever your server decides "latest" means.

C — Query param versioning (/users?version=2)
Fast to implement. Zero client SDK changes. Cache-unfriendly — every CDN layer treats ?version=1 and ?version=2 as separate cache keys. Works until you have 40 endpoints and version drift becomes untrackable.

D — Content negotiation (Accept: application/vnd.api.v2+json)
The REST-purist approach. Semantically correct — you're asking for a representation, not a route. Almost nobody implements it right. Client library support is inconsistent. One wrong Accept header and you get a 406.

Which strategy does your team use?

Top comments (5)

Collapse
 
thejoud1997 profile image
Joud Awad

A — URL path versioning: the pragmatic default (not wrong, but has a cost)

This is what Stripe, Twilio, and GitHub use. Not because it's theoretically pure — it isn't — but because it's operationally obvious.

Every proxy, log aggregator, and CDN understands it without configuration. /v1/ vs /v2/ is visible everywhere without instrumentation.

The cost is real: you're running parallel route trees, and v1 maintenance debt compounds fast. A bugfix in shared business logic has to be patched in both versions. Stripe's answer was to pin clients to a version at the API key level and deprecate slowly over years. Most teams don't have that discipline — so v1 quietly rots while v2 gets all the attention.

Collapse
 
thejoud1997 profile image
Joud Awad

B — Header versioning: good theory, rough edges

Clean architecture. The URL represents a resource, the header represents the negotiation. This is how internal microservices often version — when you control both client and server, it works well.

The problem: invisible in browser tabs, impossible to share as a curl command without extra flags, and "default to latest" on a missing header is a footgun. In year two, a legacy client quietly drops the header. Your server defaults it to v2. The client breaks. Nobody knows why — it's not in the URL, it's not in the logs unless you specifically instrumented for it.

Collapse
 
thejoud1997 profile image
Joud Awad

C — Query params: the quick fix that stays forever

Fast to implement. Zero client SDK changes. Works immediately.

Cache invalidation gets messy at scale — every CDN layer treats ?version=1 and ?version=2 as separate cache keys, so your cache hit rate tanks. The bigger problem is version drift: with no forcing function, clients scatter across v1/v2/v3 with no deprecation pressure. Good for internal tools where you control all the clients. Gets expensive for public APIs.

Collapse
 
thejoud1997 profile image
Joud Awad

D — Content negotiation: correct spec, wrong reality

Accept: application/vnd.api.v2+json is what the HTTP spec intended. You're asking for a specific representation of a resource, not a different route. Semantically, it's the most correct.

In practice: Accept header parsing is inconsistent across client libraries. Middleware strips headers silently. A misconfigured client gets a 406 Not Acceptable with no obvious error message pointing to the version field. GitHub tried content negotiation early in their API and moved to URL versioning. That tells you something.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.