What you'll learn: By the end of this lesson you will be able to explain why "client" and "server" name roles in one conversation and not kinds of machine, say what statelessness means for HTTP, separate a layer (a slice of code) from a tier (a slice of deployment), decide what a client may and may not be trusted with, and judge whether one more tier earns its extra network hop.
Think of a restaurant. The diner does not walk into the kitchen: a waiter takes the order, the kitchen cooks, and the pantry holds the stock. The diner can ask for anything, but the waiter checks the menu and the kitchen decides what is actually possible. Each station can be staffed, closed or moved separately, and each hand-over between stations takes a little time and can go wrong.
That is client-server and N-tier architecture in one picture. The ShopStream brief introduces both ideas as part of a first launch. This lesson goes deeper on the question underneath them: where does each piece of work run, and who is trusted to do it?
The core idea
A client starts a conversation and a server answers it; N-tier then describes how the server side is cut into separately deployed pieces. Both ideas are about boundaries: who may ask, who decides, and which boundaries are a network hop.
Client and server are roles
RFC 9110 (HTTP Semantics, section 3.3) says the terms client and server "refer only to the roles that these programs perform for a particular connection", and that the same program might act as a client on some connections and a server on others. A shopper's phone is the client when it asks ShopStream for products. ShopStream's application server is then the client when it asks the payment provider to charge a card.
Figure 1. One program, two roles. How to read this: time runs downward. The application server answers the phone, so it is the server there. It then calls the payment provider, so it is the client there. The roles belong to each connection, not to the machine.
HTTP is a request/response protocol: the client sends a request message and the server returns a response (section 3.4). It is also stateless: each request message's semantics can be understood in isolation, and the relationship between connections and messages has no impact on how they are interpreted (section 3.3). The server needs no memory of earlier requests to know what this one means.
Minimal example: the exchange for one browse request, with illustrative content and most headers left out.
GET /products?store=17&q=milk HTTP/1.1
Host: shopstream.example
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
{"items":[{"sku":"milk-1l","price":"1.20"}]}
Statelessness is a property of the protocol, not a promise that the application keeps no data: the database remembers everything. It lets a tier run as identical copies: in the Azure page's reference layout the web and business tiers are stateless, so "any VM can handle any request for that tier". See load balancing.
Where the trust boundary sits
The client is code you do not control. A browser extension, a modified app or a script can send any request it likes. So the useful split is not "front end versus back end" but what must be decided by something you control.
| Decision | Where it must be made | Why |
|---|---|---|
| Is this price right? | Server | The client could send any number |
| Is the item in stock? | Server | Only the server sees every shopper's orders |
| May this user see this order? | Server | Hiding a button is not access control |
| Is this input valid? | Server (the client may also check, for feedback) | OWASP: client-side validation is bypassable |
| Does the email field look right? | Client, as a convenience | Fast feedback; the server still re-checks |
The OWASP Input Validation Cheat Sheet says to validate on the server even when the browser checks the same fields, to use client-side validation only for immediate feedback, and to validate data from internal APIs, queues and stored records when it crosses a trust boundary: "an internal transport does not establish that a value meets the receiving component's rules".
Failure example: ShopStream's first checkout form posts {"sku": "milk-1l", "qty": 2, "unitPrice": 0.01} because the app computed the price in the browser and the server trusted it. Anyone who edits that request pays one cent. The fix is to accept only the SKU and quantity and look the price up on the server.
Layers and tiers
The Azure Architecture Center defines N-tier as dividing an application into logical layers and physical tiers. Layers separate responsibilities and manage dependencies: a higher layer can use a lower one, never the reverse. Tiers are physically separated and run on separate machines; you might host each layer in its own tier, but several layers can share one tier.
HTTP in and out"] --> R["Rules layer
pricing, stock checks"] R --> D["Data access layer
queries"] end D --> DB[("Database tier")]
Figure 2. Layers are in the code, tiers are in the deployment. How to read this: all three layers sit inside one box, which is one tier and one deployable unit. Calls between layers are function calls. Splitting a layer into its own tier would turn that call into a network request.
The page names two arrangements. In a closed layer architecture a layer can only call the next layer immediately down, which limits dependencies but can create traffic when a layer only passes requests along. In an open layer architecture a layer can call any layer below it. For tiers, the strict approach "has greater latency and overhead" and the relaxed one "has more couplings, which makes changes more difficult".
Figure 3. A three-tier deployment with the trust boundary marked. How to read this: the boundary is between untrusted clients and the first tier you operate. The data tier accepts connections only from the application tier, which matches the Azure page's advice to restrict access to the data tier to requests from a middle tier.
What each tier costs
The Azure page puts the trade plainly: physical separation of tiers "improves scalability and resiliency but also adds latency from the extra network communication". Among its challenges: a middle tier that only does basic create, read, update and delete (CRUD) work "adds latency and complexity without delivering meaningful value", and requests crossing several tiers make testing and monitoring harder.
There is an availability cost too. The AWS reliability pillar says that where a system has hard dependencies on others, its availability is the product of theirs. With an illustrative 99.9% for each tier, three tiers in a chain give 0.999 x 0.999 x 0.999 = 0.997003, about 99.7%, and four give 0.999 to the fourth power, about 99.6%. Redundancy inside a tier can win some back (reliability basics).
on its own terms?"} -->|"no"| L["Keep it as a layer in the existing tier"] Q1 -->|"yes"| Q2{"Is an extra network hop and its failure
mode acceptable here?"} Q2 -->|"no"| L Q2 -->|"yes"| T["Make it a tier, with a timeout and monitoring on the hop"]
Figure 4. Should this be a new tier? How to read this: start at the top for each candidate. A tier needs a named reason (separate scaling, separate security or separate failure handling) that a layer inside an existing tier cannot meet.
| Option | Gain | Cost | Wrong when |
|---|---|---|---|
| Client talks straight to the database | No server to run | Credentials in every client, rules copied everywhere | Users are untrusted or numerous |
| Two tiers: app server plus database | Few moving parts, easy to test | The app server does everything; one of each is a single point of failure | Web and logic work must scale or be secured apart |
| Three tiers: web, application, data | Each tier scales and is secured separately | More machines and hops | A tier would only forward calls |
The other side of this topic
The browser is also an architecture, with its own layers (view, state, an API client) and its own constraints. The Frontend System Design track covers that side; this lesson stays on the server side of the boundary.
In practice: ShopStream decides whether to add a tier
This is the realistic example, on the course's running example, ShopStream. Numbers are the course's illustrative targets.
This is a realistic scenario, not a case history.
The situation
ShopStream launches as a two-tier system: one application server and one database (Stage 1). The target scale is 2,000,000 daily active users with 20 page views each: 40,000,000 page views a day, about 463 a second on average and about 2,300 at an assumed peak of five times the average. The team asks whether to add a web tier now, a "pricing tier" between the app and the database, or neither.
Starting architecture
Figure 5. The two-tier starting point. How to read this: one application process holds all layers, and one database holds the data. Every extra box in the options below adds a hop to this path.
Options considered
| Option | Gain | Cost | Risk |
|---|---|---|---|
| A. Stay at two tiers | Simplest to run and trace | One server does TLS, files and logic | The web work cannot scale apart |
| B. Add a web tier (reverse proxy, TLS, static files) | Static work and TLS leave the app server; a place to add copies of the app | One more hop; one more thing to patch | A tier that only forwards adds latency for nothing |
| C. Add a pricing tier between app and database | Pricing could scale on its own | A network hop on every browse request | A pass-through tier with no separate scaling need |
The decision
Option A at launch, and option B when the team runs more than one application server, because a web tier is the natural place to spread traffic and end TLS. Option C is rejected: pricing is a layer in the rules code, and nothing yet scales or fails apart from the rest. Revisit when one layer needs to scale on its own.
Rollout and verification
Add the web tier in front of one application server first, compare error rate and response-time percentiles with the old path, and keep a one-step rollback. Verify that the data tier accepts connections only from the application tier, and that a test posting a changed price sees it ignored.
The 2 a.m. failure
Checkouts fail with gateway errors while the web tier looks healthy. The application tier has run out of database connections and answers slowly, so the web tier times out and returns an error. Check each tier's own error rate and latency, and follow one request ID through every tier's logs. Mitigate by shedding load or restarting the stuck application servers, then fix the pool limit. The lasting fix is a timeout on every hop shorter than the caller's own, and a dashboard per tier, since the first symptom shows in a tier that is not at fault.
Architect's notes
- Draw the trust boundary first. Everything on the client side of it is advice, not enforcement.
- Add tiers for a named reason. "Scales on its own", "needs a different security zone" or "fails on its own terms" are reasons; "the diagram has three boxes" is not.
- Put a timeout and a metric on every hop. Each tier boundary is a place where latency and failure appear.
Common mistakes
- Treating client and server as machine types. They are roles in one connection.
- Trusting data because it came from "our" client. Validate on the server.
- Confusing layers with tiers. Layers organise code, tiers are deployments.
- A pass-through middle tier. It adds a hop and a failure point for no gain.
Key takeaways
- Client and server are roles for one connection; one program can be both.
- HTTP is stateless: each request can be understood on its own, so state travels with the request or lives in a store, and tiers can run as identical copies.
- Decide prices, stock and access on the server; client checks are for feedback only.
- A layer is a slice of code, a tier is a slice of deployment, and several layers can share one tier.
- Each extra tier adds latency and a failure point, and the availabilities of hard dependencies multiply.
- Add a tier for a named scaling, security or failure reason, never by default.
Practice exercises
Exercise 1 (20 min). Take an app you know. For five of its requests, name the client and the server, and say whether either also plays the other role on another connection.
Exercise 2 (30 min). List ten checkout decisions (price, stock, address format, coupon validity) and mark each server-only, client-as-convenience, or both.
Exercise 3 (30 min). Compute the series availability for two, three and four tiers at 99.95% each, then for the same chain where the middle tier has two independent copies (use the redundancy formula in the AWS page).
Related reading
- The Big Picture: the whole stack in one tour.
- ShopStream brief: the same ideas at the project's first stage.
- Architecture styles: monolith, modular monolith and microservices.
- Request lifecycle: one request end to end.
- Load balancing: spreading requests across copies of a tier.
- Reliability basics: redundancy and availability maths.
Official documentation
- RFC 9110: HTTP Semantics: sections 3.3 and 3.4, client and server roles, statelessness, request and response.
- N-tier architecture style, Azure Architecture Center: layers, tiers, closed and open layers, benefits and challenges.
- OWASP Input Validation Cheat Sheet: server-side validation and trust boundaries.
- AWS Well-Architected reliability pillar: Availability: hard dependencies multiply availability.