Mo Sharif
Back to writing

System Design Lessons From 55 Architecture Studies

In this article

The first thing I noticed while studying architectures for Codelit's reference library was how familiar the components were. Databases, queues, caches, workers. The hard part was not discovering another technology. It was understanding why a team put a boundary in one place rather than another.

A reference architecture

is a study model of services, data stores and failure boundaries, reconstructed from public sources to explain a design rather than certify a company's current infrastructure.

This is what I took from a set of 55 architecture studies. It is not a statistical survey, and the diagrams should not be mistaken for complete production inventories.

Queues appear when a system needs to accept work independently of processing it. That separation can absorb bursts and isolate a slow consumer from a user-facing request. It also creates a new obligation: track backlog, retries and the age of unfinished work.

The practical question is not “Should this system have Kafka?” It is “Does this operation need to finish before I answer the user?”

For an image upload, storing the original may need to finish immediately while thumbnail generation can happen later. For a payment, telling the user “accepted” and telling them “paid” are different promises.

Once the work moves to a queue, draw the failure path too:

  • What happens when a message arrives twice?
  • How long can the oldest job wait?
  • Who investigates a job that exhausts its retries?
  • Can the producer keep accepting work faster than consumers can finish it?

A monolith can use a queue. A distributed system can have a sensible synchronous path. Deployment shape does not answer the timing question.

Caching is the right move when repeated work dominates a measured bottleneck and the product can define acceptable freshness. A useful cache design explains misses, invalidation and failures. Otherwise the cache can move the bottleneck or serve incorrect data faster.

Before adding capacity, I would ask whether we can avoid doing the work at all. But I would also ask what happens when the cache is empty.

A design that survives only with a warm cache has a recovery problem. A popular key can create a stampede. A cached permission decision can outlive the permission. Those are not arguments against caching; they are part of designing it.

DecisionQuestion to answer
Cache keyWhich inputs actually determine the result?
FreshnessHow stale may this value be?
Miss behaviorCan the source absorb cold-start traffic?
InvalidationWhat event makes the cached value unsafe?
FailureDoes the request fail open, fail closed or use stale data?

The answer can be different for a product image and an account balance. The same “Redis” box does not mean the same design.

Stripe's idempotency mechanism allows a client to retry a supported request using the same key and parameters without executing the same operation again while the result is retained. The guarantee has documented boundaries, so it should be treated as an API contract rather than a general exactly-once promise.

Stripe's documentation explains that saved results can include failures, that parameter mismatches are rejected and that keys can be removed after they are at least 24 hours old. Validation failures and concurrent conflicts have additional retry considerations.

The design lesson I take from this is to name the logical operation first. An order attempt gets a stable key; a genuinely new attempt gets a new one. Retrying with a new key defeats the mechanism.

The rest of the application still needs durable state and reconciliation. A provider accepting a request and your worker recording that acceptance are two separate events. That gap is also central to agent workflow orchestration.

Netflix's Open Connect changes where content is served by placing delivery infrastructure close to viewers through ISP partnerships and interconnection. The lesson is to question data movement before scaling a distant origin. It is not evidence that every application should build a private CDN.

Netflix describes Open Connect as its content delivery network, with embedded appliances and peering arrangements. That is a concrete example of distribution becoming an architecture decision.

For a smaller product, the same question can lead to an ordinary CDN, regional storage or a different asset format. Copy the reasoning about distance and demand. Do not copy the infrastructure budget.

Service boundaries earn their complexity when independent deployment, ownership, scaling or failure isolation solves a real constraint. Splitting code alone does not create those properties. Shared state and synchronous dependencies can preserve the coupling while adding network and operational failure modes.

A gateway is a good example of why a box count is misleading. One gateway on a diagram may represent a redundant managed service, not a single process. Several gateways may isolate deployments, or they may merely duplicate configuration.

I would ask which changes can ship independently, which failures remain local and who owns recovery. That is a better review than declaring one gateway wrong or several hundred services impressive.

Breaking a diagram deliberately is useful for testing the declared dependencies. It is still a model; undeclared coupling needs evidence from the actual system.

Try the smallest intervention that addresses the measured constraint, and write down the failure mode it introduces. There is no honest percentage assigning most scaling problems to caches or the remainder to sharding. Different workloads reach different limits.

Observed constraintCandidate interventionCost to investigate
Repeated expensive readsQuery improvement or cachingFreshness and invalidation
Read load exceeds one primaryRead replicasReplication lag and routing
Slow work blocks responsesAsynchronous processingDuplicate work and backlog
One hot partition dominatesA different key or partition strategyMigration and skew
Teams cannot ship independentlyA clearer module or service boundaryContracts and operating ownership

This is also where design-time cost estimation belongs. A new component should arrive with a reliability argument and a budget, not just an arrow.

Turn a reference diagram into an explanation you can reconstruct without looking at it. Follow one request, name the state it changes and describe what happens if the next step fails. The gaps in that explanation are the next things to study.

I use this loop:

  1. Read the primary source and note its date and scope.
  2. Redraw one path rather than the whole company's architecture.
  3. Label what is known, inferred or deliberately simplified.
  4. Change one constraint and explain which decisions must change.
  5. Compare the result with the source again.

That is the distinction behind teaching system design through a canvas. A polished diagram makes a design easier to discuss. It does not remove the need to understand it.

The Codelit template library is a place to start the exercise. The goal is not to reproduce the picture. It is to explain why each component deserves to remain.

Questions people actually ask

What do reference architectures teach beyond the component list?
The useful lesson is the constraint behind each component: which work must finish now, which can wait, what data must stay consistent and what failure the system tolerates. A reference diagram is a study model reconstructed from public material, not an authoritative map of a company's current infrastructure.
Do I need a message queue in every architecture?
No. A queue helps when work can happen asynchronously, bursts need buffering or consumers need independent retries. It also introduces backlog, duplicate delivery and operational work. A simple synchronous path is reasonable when its latency and failure behavior meet the requirement.
Is caching better than microservices for scaling?
The comparison depends on the bottleneck. Caching removes repeated work when stale data is acceptable. Service separation can support independent scaling, ownership and isolation. Neither is a universal first step; measure the workload and name the constraint before choosing one.
What does a Stripe idempotency key guarantee?
A supplied idempotency key lets Stripe recognize retries of a supported request and return the stored result rather than repeat the operation. Keys must be reused for the same logical attempt with matching parameters, and Stripe documents retention and execution-related limits.
How should I practice with a reference architecture?
Read the source and draw one request path yourself. State its assumptions, remove a component and explain what changes. Then introduce a failure and describe detection and recovery. The exercise is useful when it exposes a question the finished diagram concealed.