The problem
Without structure, UI code talks to SQL, business rules hide in controllers, and a change to the database ripples through every screen. Layering is the oldest answer: group code by technical responsibility and control the direction of dependencies.
How it works
- Closed layers: a request must pass through each layer in turn. This keeps the isolation.
- Open layers: some layers can be skipped, such as a shared utilities layer. Use them sparingly, or the isolation disappears.
- Each layer exposes an interface to the layer above it and hides its internals.
- Small-to-medium apps and CRUD-heavy line-of-business systems
- Teams that are new to the domain and need a familiar structure
- You need to ship fast and the domain logic is thin
- Domain logic is rich and must be tested without the DB
- Many teams need to deploy independently
- You notice the 'sinkhole' anti-pattern: most requests just pass straight through every layer
Trade-offs
- ✅ Simple, well understood, easy to onboard people.
- ✅ Clear place for each kind of code.
- ❌ The domain layer usually ends up depending on the data layer, so business rules get coupled to persistence. Hexagonal and Clean architecture fix this by inverting that dependency.
- ❌ It's a technical partitioning. A single feature change touches every layer, which makes team ownership awkward.
My early .NET systems (the time-tracking system at DNEG, line-of-business apps at Prime Focus) were classic 3-tier: ASP.NET → service layer → SQL Server. They were quick to build, but once rules grew, the stored procedures started holding business logic. That's the moment to move toward a domain-centric style.
In one line
Layered architecture partitions code by technical concern and enforces top-down dependencies. It's the right default for simple apps, but I invert the domain → data dependency once business rules matter.
Sources & further learning
Videos, courses, docs and books I recommend for this topic.
Related topics
Hexagonal Architecture (Ports & Adapters)
Put the business core at the centre and talk to the outside world — HTTP, DB, queues, third-party APIs — only through ports (interfaces) implemented by swappable adapters.
Clean Architecture
Concentric rings — entities, use cases, interface adapters, frameworks — with one rule: source-code dependencies only point inward.
Modular Monolith
One deployable unit, internally split into strongly bounded modules with explicit public APIs — microservice-style boundaries without the distributed-systems tax.