Layered / N-Tier Architecture
The classic architectural style: presentation, business, data access, and database tiers. When layering helps and when it hinders.
The Layered Pattern
Layered (N-Tier) architecture organizes code into horizontal layers, each with a distinct responsibility. Layers only communicate downward — the layer above depends on the layer below. This separation makes the codebase predictable: you always know where to find presentation logic, where business rules live, and where database calls are made.
The classic three-tier decomposition is Presentation → Business Logic → Data Access → Database. The 'N' in N-Tier means you can add layers as needed — a common extension is a Service Layer between business logic and data access to encapsulate use cases.
Layer Responsibilities
| Layer | Responsibility | Examples |
|---|---|---|
| Presentation | Handle user input, render output, HTTP routing | Express.js routes, React components, Django views |
| Business Logic | Domain rules, validation, use-case orchestration | OrderService.placeOrder(), PricingEngine.calculate() |
| Data Access | Abstract database calls, query building, mapping | UserRepository.findById(), ActiveRecord models |
| Database | Persist and query data | PostgreSQL, MongoDB, Redis |
The Strict vs Relaxed Layering Rule
In strict layering, each layer may only communicate with the layer directly below it. This enforces clean boundaries but can be cumbersome when the presentation layer needs data from the database without any meaningful business logic transformation — you still have to pass through the business layer. Relaxed layering allows skipping layers for simple read operations, trading some purity for pragmatism.
Advantages
- Separation of concerns: Each layer has one job. A UI developer doesn't touch database code.
- Replaceability: You can swap the presentation layer (e.g., replace a web UI with a mobile API) without touching business logic.
- Testability: Business logic can be unit-tested without a database by mocking the data access layer.
- Familiarity: The most widely understood pattern — Rails, Django, Spring MVC, ASP.NET all follow it out of the box.
Disadvantages and the 'Lasagna Code' Problem
Layered architecture's primary failure mode is 'lasagna code': every change requires touching every layer. Adding a new field to a user profile means changing the database schema, the ORM model, the repository, the service, the DTO, the controller, and the view. The architecture is horizontal — easy to understand — but change ripples vertically across all layers.
The Anemic Domain Model Trap
Layered architectures often produce anemic domain models: business logic leaks into service layers or controllers because the domain objects (User, Order) are just data containers with getters/setters. The 'business logic layer' becomes a procedural script. This is a sign that the domain is not properly modeled.
// Anti-pattern: Anemic domain model
// The Order class has no behavior — just data
class Order {
id: string;
status: string; // "pending" | "shipped" | "cancelled"
items: OrderItem[];
totalPrice: number;
}
// All logic is in the service layer (procedural)
class OrderService {
async cancelOrder(orderId: string): Promise<void> {
const order = await this.repo.findById(orderId);
if (order.status !== "pending") throw new Error("Cannot cancel");
order.status = "cancelled";
await this.repo.save(order);
// and so on...
}
}
// Better: Rich domain model
class Order {
cancel(): void {
if (this.status !== "pending") throw new DomainError("Cannot cancel");
this.status = "cancelled";
this.addEvent(new OrderCancelledEvent(this.id));
}
}When to Use Layered Architecture
Layered architecture is an excellent choice for: CRUD-heavy applications where the domain logic is thin, teams with clear front-end/back-end/database specialization, enterprise applications with well-understood domains, and as the internal structure of a monolith or individual microservice.
Interview Tip
Interviewers often expect layered architecture as the default when you describe a backend service's internal structure. Be prepared to name the layers (controller → service → repository → database), explain the dependency direction, and describe how you would test each layer independently. Bonus: mention that hexagonal and clean architecture are evolutions of layered thinking that avoid the 'database-centric' trap.