PushBackLog

Layered Architecture

Advisory enforcement Complete by PushBackLog team
Topic: architecture Topic: quality Skillset: backend Skillset: frontend Technology: generic Stage: execution Stage: review

Layered Architecture

Status: Complete
Category: Architecture
Default enforcement: Advisory
Author: PushBackLog team


Tags

  • Topic: architecture, quality
  • Skillset: backend, frontend
  • Technology: generic
  • Stage: execution, review

Summary

Layered (or n-tier) architecture organises a system into horizontal layers — typically Presentation, Application/Domain, and Infrastructure — where each layer has a well-defined responsibility and may only depend on layers below it. This structure creates natural seams for testing, replacement, and scaling of individual concerns.


Rationale

Layers as dependency contracts

Layered architecture is the most common structural pattern for backend systems because it makes explicit what each part of the system is responsible for and what it is allowed to depend on. Without deliberate layering, systems converge on a “big ball of mud”: everything depends on everything, and changing any part requires understanding all parts.

Each layer boundary is a dependency contract. The presentation layer depends on the application layer but not the infrastructure layer. The application layer depends on the domain layer but not on HTTP request objects. The domain layer depends on nothing outside itself. These dependency boundaries enable substitution: swap the database from PostgreSQL to MongoDB by replacing the infrastructure layer, without touching domain or application.

Testability by layer

Layering directly enables the test pyramid. Domain logic is tested with pure unit tests — no infrastructure needed. Application services are tested by injecting test doubles for infrastructure. Infrastructure layer is tested with integration tests against a real (containerised) database. Presentation layer is tested with API-level tests or browser tests.

The evolution to hexagonal / clean architecture

Classic layered architecture (UI → Business → Data) has a well-known weakness: infrastructure (the data layer) sits at the bottom, which means the domain layer often depends on it. Hexagonal architecture (ports and adapters) inverts this: the domain is at the centre, and infrastructure is at the outside, depending on the domain via interfaces. Both are layered models; hexagonal applies DIP to remove the infrastructure dependency on the domain layer.


Guidance

Common layer map

LayerResponsibilityDepends on
PresentationHTTP/gRPC/GraphQL handling, request/response transformation, auth middlewareApplication
ApplicationUse case orchestration, transaction boundaries, command/query dispatchDomain, Infrastructure interfaces
DomainBusiness entities, business rules, domain events, aggregatesNothing outside domain
InfrastructureDatabase queries, external HTTP calls, file I/O, queue publishingDomain interfaces

Dependency rules

  • Dependencies flow downward (presentation → application → domain)
  • Infrastructure implements interfaces defined by the domain/application layer (DIP)
  • Domain objects import nothing from presentation or infrastructure
  • Use constructor injection to assemble layers at startup

Directory structure example

src/
  presentation/          # Controllers, routes, middleware
    orders/
      OrderController.ts
  application/           # Use cases / services
    orders/
      CreateOrderService.ts
      GetOrderService.ts
  domain/                # Entities, value objects, interfaces
    orders/
      Order.ts
      IOrderRepository.ts
  infrastructure/        # Concrete implementations
    database/
      PostgresOrderRepository.ts
    email/
      SendGridEmailSender.ts

Examples

Flow through layers for “Create Order”

HTTP POST /orders
  ↓ OrderController (presentation)
    - validates request shape
    - calls CreateOrderService with a Command object
  ↓ CreateOrderService (application)
    - loads products from IProductRepository
    - creates Order aggregate (domain)
    - saves via IOrderRepository
    - publishes OrderCreated event
  ↓ Order (domain)
    - applies business rules (discount cap, inventory check)
    - emits domain events
  ↓ PostgresOrderRepository (infrastructure)
    - writes to database

Each layer has a single job. Any layer can be tested by replacing adjacent layers with fakes.


Anti-patterns

1. Layer skipping

A controller calling the repository directly, bypassing the application and domain layers. This means business rules can only be enforced at the HTTP boundary, and the shortcut accumulates until the architecture is meaningless.

2. Anemic domain model

Domain objects that are pure data holders (getters/setters) with all logic in service classes. This is technically layered, but the domain layer has no content — business rules are scattered across service classes instead of being encapsulated in domain objects.

3. Infrastructure types leaking into domain

A domain entity that imports pg.Row or mongoose.Document. When the domain is aware of the database technology, the infrastructure cannot be swapped without domain changes.

4. God service

A single OrderService with 30 methods that spans all use cases and all concerns. Should be split into focused use-case handlers or application services, each with a single responsibility.



Part of the PushBackLog Best Practices Library. Suggest improvements →