Skip to content

    System Design

    Client-Server and N-Tier Architecture: Roles, Layers and Tiers

    Learn why client and server are roles, how layers differ from tiers, where trust belongs, and when adding a tier helps or only adds latency.

    By Published 10 min read

    Share

    Focused lessonbeginnerBackend System Design

    Before this: The Big Picture

    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.

    sequenceDiagram accTitle: The application server plays both roles accDescr: A phone sends a request to the application server, which acts as a server. The application server then sends a charge request to the payment provider, acting as a client, and relays the result back. participant P as Shopper phone participant A as App server participant Y as Payment provider P->>A: POST /checkout Note over P,A: the phone is the client, the app server is the server A->>Y: charge the card Note over A,Y: the app server is now the client Y-->>A: approved A-->>P: 200 order confirmed

    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.

    flowchart TD accTitle: Three layers inside one application tier accDescr: HTTP handlers call business rules, which call data access, which talks to the database tier. The three layers run in a single application process. subgraph T["Application tier: one process"] H["Handlers layer
    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".

    Three tiers with the trust boundary between clients and the web tierBrowsers and apps to Web tier (HTTPS); Web tier to Application tier (forward); Application tier to Data tier (SQL)YOU DO NOT CONTROLYOU OPERATE AND SECUREBrowsers and apps: untrusted codeBrowsers andappsuntrusted codeWeb tier: TLS, static filesWeb tierTLS, static filesApplication tier: rules, validationApplicationtierrules, validationData tier: app tier onlyData tierapp tier onlyHTTPSforwardSQL

    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).

    flowchart TD accTitle: Deciding whether to add a tier accDescr: A decision flow. If the piece needs different scaling, security or failure handling, and a network hop is acceptable, make it a tier. Otherwise keep it as a layer inside an existing tier. Q1{"Does this piece need to scale, be secured or fail
    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

    ShopStream's two-tier starting pointShoppers to Application server (HTTPS); Application server to Database (SQL); Application server to Payment provider (charge card)APPLICATION TIERDATA TIERShoppers: web and mobileShoppersweb and mobileApplication server: all layersApplicationserverall layersDatabase: orders, stockDatabaseorders, stockPayment providerPaymentproviderHTTPSSQLcharge card

    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).

    Official documentation

    Ask about this article

    Get answers grounded in this post. AI-generated — based on this article, and may be imperfect.

    Scaled AI Weekly

    Enjoyed this? Get more like it every Monday.

    Real architecture decisions, LLMOps patterns that survive production, and engineering leadership advice — from 12+ years of building at enterprise scale. Free. No spam. Unsubscribe anytime.

    Get new articles by email

    Free. We email a confirmation link first, and you can unsubscribe from any email.

    Join engineers building production AI systems

    Comments