Client-Server & API-Centric Architecture
Software Architecture Fundamentals
Chapter 8 · Client-Server & API-Centric Architecture
"REST" gets used loosely to mean "an HTTP API with GET/POST/PUT/DELETE" — but its actual defining architectural constraint is statelessness: every request carries everything the server needs, and the server keeps no memory of a client between requests. This chapter verifies why that constraint matters, and then looks at the other half of client-server architecture: how much logic a client should hold at all.
Statelessness, Verified — Not Just Defined
'Widget' via StatefulCartService correctly reports ['Widget']. Creating a fresh instance of the same service (simulating a server restart — a new process, a load balancer routing to a different server, anything that discards in-memory state) and calling get_cart('SESSION-1') with the identical session ID returns [] — genuinely empty. The cart didn't survive.
StatelessCartService: adding 'Widget' returns ['Widget'], which the client holds onto. A fresh service instance, given that same ['Widget'] cart by the client itself, correctly returns ['Widget'] — because the server was never the one remembering it. Nothing about the server's own restart mattered, because nothing the request needed was stored there.
Thin vs. Thick Clients: Where Should the Logic Live?
A thick client implements business logic itself. A thin client only displays what the server tells it and sends raw requests. What happens when the same logic gets implemented twice, independently, by two thick clients?
$120 order for a loyalty member: mobile_client_calculate_total() returns 103.0; web_client_calculate_total() returns 103.5. Both teams implemented "10% loyalty discount, then $5 off orders over $100" — but disagreed on the order those two rules apply in, producing a genuine $0.50 discrepancy for the same customer, the same order, on two different platforms.
The Thin-Client Fix: One Server, Both Clients Call It
/calculate, backed by a single server_calculate_total() function, and having both a "mobile" and "web" client call it (instead of computing anything themselves): both correctly return 103.5 — identical. Neither client contains the discount logic at all anymore; both just display whatever the one authoritative implementation returns.
Where a Mobile App and a Web Frontend Both Fit
This is the actual payoff of API-centric architecture: build one stateless, thin-client-facing API, and let as many different client types as needed — a web frontend, a mobile app, a third-party integration — consume the identical endpoints. None of them need their own copy of the business logic, and none of them depend on the server remembering who they are between requests.
Where This Connects
| This chapter's finding | What it connects to |
|---|---|
| A stateful cart losing data on a simulated restart | Distributed Systems & Scalability's own Load Balancing chapter — this is precisely the failure mode session affinity exists to work around, and statelessness avoids needing it at all |
| A verified $0.50 discrepancy between two independently-implemented thick clients | Chapter 5's coupling analysis — duplicated logic across two codebases is a coupling problem, even with no direct function call or shared data between them |
| One authoritative server endpoint resolving the discrepancy | Chapter 7's Hexagonal Architecture — the server's own server_calculate_total() is exactly the kind of core logic a real system would put behind ports, testable independently of any client |
Hands-On Exercises
Add a get_item_count(current_cart) method to this chapter's own StatelessCartService. Verify it works correctly even when called against a brand-new service instance (simulating another server restart), given only the cart data the client itself provides.
Using this chapter's own mobile_client_calculate_total() and web_client_calculate_total(), find a second input (a different items_total, still a loyalty member) where the two thick clients happen to agree, and explain specifically why the order-of-operations bug doesn't show up for that input.
This chapter's own server_calculate_total() matched the web client's own (correct-by-luck) order of operations, not the mobile client's. Explain why "the server happens to agree with one of the two clients" isn't actually the reason a thin-client design fixes the discrepancy — what's the real reason, using this chapter's own verified $120 example?
Chapter 8 Quick Reference
- Statelessness, verified: a stateful cart lost its contents (
[]) on a simulated server restart; the stateless version, given the same data by the client, survived unchanged - Thick clients, verified diverging: two independently-written clients returned
103.0vs.103.5for the identical $120 loyalty-member order — a real $0.50 discrepancy from independently-implemented rule ordering - Thin clients, verified converging: both clients returned the identical, correct
103.5once calling one shared server endpoint instead of implementing the logic themselves - Next chapter: Documenting Architecture — ADRs and the C4 Model, so a decision like "thin client, stateless API" gets recorded, not just made