Layers and Deployment Boundaries
How does a layered architecture differ from a monolithic architecture? The two words answer different questions.
- Layered is how the code is organized: separate layers, each with one responsibility.
- Monolithic is how the code is deployed: one unit with one deployment boundary, released together.
An application can be both. The shop is layered and monolithic. Its three layers run in one application. The API layer calls a business operation through an ordinary function call, and that operation calls the data access code the same way. We package and deploy the backend as one unit, and a change to the data access code still means a new release of the whole backend.
Layers and tiers
A layer is a division of the code by responsibility. A tier is a division of the system by where things run. People sometimes use the two words loosely, so keep them apart when you read a design.
The shop has three tiers: the client, on the shopper’s device; the application server, where the backend runs; and the database server. All three layers run on the application server. The data access layer runs there too, even though it talks to the database on another server. If we duplicate the backend behind a load balancer, every instance holds all three layers, and all the instances belong to the application tier.
graph TB
W[Website] --> A
P[Phone app] --> A
subgraph "Application server: one deployment boundary"
A[API layer] --> B[Business logic layer]
B --> D[Data access layer]
end
D -->|over the network| DB[(Database server)]
Moving a layer out
Suppose we move the business logic and its data access code into a service of their own, deployed separately. The API layer now sends it a request over the network. We have added a deployment boundary.
The benefit is that we can release a change to the business logic without redeploying the API layer, as long as the interface between them stays compatible, and we can run different numbers of instances for the API layer and the business logic service.
The cost is that a call that used to stay inside one process now takes a network round trip. The business logic service can be down. It can finish placing an order while its response is lost, leaving the API layer unsure whether the order was placed, which is the same uncertainty our clients handle with idempotency keys. And there is another service to operate and another interface to keep compatible across releases.
For now, I would keep the shop’s layers in one backend. The shop is small, and both APIs can call the same operations inside one application. If traffic later exceeds one instance’s capacity, we can duplicate the whole backend behind a load balancer, as we did for the registrar. So deciding which code belongs together and deciding which parts must run separately are two different decisions.