How to design anything
How do you go from a vague idea to a design you can defend?
Where do you start when someone says “an app like the big ones”?
The site works now. The server sits in a nearby region, orders take a fraction of a second, and nobody has ordered two lunches since.
Then the owner of the kitchen watches a neighbour order groceries on one of the big delivery apps: live tracking, saved addresses, a rider on a map. The owner wants the same, and makes the request that every small system gets sooner or later: make our app like that one.
The usual first suggestion is to do it properly from the first day, with microservices, Kubernetes and an event bus, because that is how the big apps are built.
So the design starts as a list of names: an order service, a menu service, Kafka. Soon there are ten technologies on the list, and not one sentence that says why any of them is needed. Which part gets built first? How will anyone know when the design is finished? How would it be explained to the person who asked for it, who will certainly ask?
A caterer feeding a wedding asks three things first: how many guests, what they eat, and when they arrive. The pots come last. Before reading on, write the ordering site’s version of those three questions. Then look at the list of ten technologies and try to connect any one of them to any one of your answers.
Pause and predict You have a list of ten technologies and cannot say why you need any of them. What should come first?
Starting from the technology
The most common way to design badly is to begin with components. Here is the day-one plan, drawn properly. Every box is a real, respectable technology.
No box points back to a reason. Ask “why is there an event bus?” and the only answer is “because big companies have one”. When something goes wrong, nobody can say which requirement a box exists to protect, so nobody knows which box can be removed.
Every box also has a running cost: something to deploy, monitor, upgrade and keep alive at 2 a.m. For a team of one, all of it lands on one person.
Five steps, each with its own question
The method is five steps, always in this order. Each step answers one kind of question, and each produces an artifact you can show someone.
- Requirements. What must it do (functional requirements), how well (non-functional requirements: latency, availability, scale), and for whom? End with a two-minute sketch of the core entities: the nouns, such as customer, menu, order.
- Logical design. Which parts exist and who talks to whom? Boxes and arrows, no machines yet.
- Data design. What is stored, how it is looked up, and how it stays correct.
- API design. The exact contract between the parts: requests, responses, errors, and the order things happen in.
- Physical design. Where it runs: machines, regions, copies, and what happens when one dies.
Then, at the end, the decisions you made on the way are written down, each with what it cost.
Pictures for the steps: the C4 model
- The C4 model
- A way of drawing software at four zoom levels, like a map that goes from country to city to street. Each level is a separate diagram for a separate audience.
- For example: Context: the ordering system as one box among its users. Container: that box opened up into a web app, a server and a database.
Zoom between the first two levels for the ordering system as it is today:
In C4, a container means anything that runs separately or stores data: a web app, a server process, a database. It has nothing to do with Docker containers. The Context diagram is for anyone; the Container diagram is for the people who build it.
Three of the C4 diagrams line up with three of the steps:
| Step | C4 diagram | What fills the gap |
|---|---|---|
| Requirements | System Context | A list of requirements and a table of targets |
| Logical | Container | |
| Data | none | An entity-relationship (ER) diagram and a table of access patterns |
| API | none (Dynamic, used sparingly) | An endpoint table and a sequence diagram |
| Physical | Deployment | Capacity numbers and a failure-mode table |
What each extra box costs
The day-one plan splits one order into calls between services. Two numbers show what that costs before any benefit arrives.
Time. Calling a function inside one process takes nanoseconds. A network round trip inside one data centre takes about 0.5 ms. An order that passes through the gateway, the order service and the menu service in turn makes at least three such trips: about 1.5 ms. That is small, and it is paid on every request forever.
Availability. This one is larger. If an order needs four separate services and each is up 99.9% of the time, independently, the order works only when all four are up:
| Design | Working | Order works | Down per 30 days |
|---|---|---|---|
| One server process | 0.999 | 99.9% | 43.2 min |
| Four services in a chain | 0.9994 = 0.99600 | 99.6% | 172.5 min |
| Five services in a chain | 0.9995 = 0.99501 | 99.5% | 215.6 min |
30 days is 43,200 minutes. One part at 99.9% is down 0.1% of that: 43.2 minutes. With four parts in a chain, the order fails when any one of them is down, so it fails roughly four times as often: 0.4% of the time, about 172.5 minutes. Splitting a system can make each part simpler, but unless each part also becomes more reliable, the system as a whole becomes less reliable.
One program or many services?
Large companies do run microservices. The question is whether the reasons that drove them apply to a team of one and twenty orders a day.
The benefits of services are real, and most of them are benefits for many teams: deploying without waiting for each other, owning a service each. With one engineer there is nobody to wait for. The point that a bug in the menu should not crash ordering is fair too, and a codebase split into clean modules gets part of that protection without any network calls.
The decision record matters as much as the decision. When the kitchen has three teams and a menu that changes every hour, someone will read this record, see that its context no longer holds, and make a new decision.
What to remember
- Design in five steps, in order: requirements (ending with the core entities), logical, data, API, physical. Then record the decisions.
- Each component must point back to a reason: a requirement, a number or a failure. If you choose a technology before you have a reason for it, nobody can tell later whether it is needed.
- C4 draws three of the steps: Context for requirements, Container for logical design, Deployment for physical design. Data and APIs need an ER diagram, an endpoint table and a sequence diagram beside it.
- Every extra service in the path of a request adds a network hop and multiplies availabilities: four parts at 99.9% give about 99.6%.
Write the requirements down before you choose a single technology.
In the real world
Professional architecture methods are larger than five steps. arc42 has twelve sections; Kruchten’s 4+1 model has five views. The five steps here are a subset that maps onto both, and onto every major interview framework.
Real teams also loop back. A failure found in the physical step often changes the logical design. The order of the steps is the order you start them in, not a promise never to revisit.
Once a few designs feel familiar, run the steps against a clock: the mock interview deals you a prompt, gives you 45 minutes, and has an interviewer cut in.