Layered Architecture

Learning Objectives

Structure an application into presentation, application, domain and infrastructure layers.

Keep dependencies pointing inward/downward correctly; map DTOs; place validation.

Prerequisites: Modules 18, 21, 22.

Intuition and Definition

UI, HTTP controllers, CLI; input/output formats Application (service)

Use cases: orchestrate domain objects, transactions, security

Business rules, entities, value objects, domain services nothing (ideally)

Databases, messaging, file systems, external APIs Domain/application *interfaces*

In the classic version the domain depends on infrastructure (top-down); the improved version inverts that dependency — infrastructure implements interfaces declared by the domain/application — which leads directly to hexagonal and clean architecture.

Why It Matters

Layers separate concerns that change for different reasons: UI frameworks, business rules and databases evolve independently.

Syntax and Language Notes

Enforce layers with project/module boundaries (separate assemblies, Java modules, C++ libraries) and architecture tests (ArchUnit, NetArchTest).

Complete Example

#include <iostream>
#include <map>
#include <optional>
#include <stdexcept>
#include <string>

// ===== domain =====
class Reservation {                                   // entity with rules
    std::string id_; int seats_;
public:
    Reservation(std::string id, int seats) : id_(std::move(id)), seats_(seats) {
        if (seats < 1 || seats > 8) throw std::invalid_argument("1..8 seats per reservation");
    }
    const std::string& id() const { return id_; }
    int seats() const { return seats_; }
};
struct ReservationRepository {                        // port declared BY the domain
    virtual ~ReservationRepository() = default;
    virtual void save(const Reservation&) = 0;
    virtual int seatsTaken() const = 0;
};

// ===== application =====
struct BookRequest { std::string customer; int seats; };          // DTO in
struct BookResult { bool ok; std::string message; };             // DTO out
class BookingService {                                            // use case
    ReservationRepository& repo_;
    int capacity_;
    int next_ = 1;
public:
    BookingService(ReservationRepository& r, int cap) : repo_(r), capacity_(cap) {}
    BookResult book(const BookRequest& req) {

…
201 Created: reserved R1 for Ana
409 Conflict: 1..8 seats per reservation
201 Created: reserved R2 for Lee
409 Conflict: fully booked

Step-by-Step Walkthrough

The domain Reservation enforces "1..8 seats"; it knows nothing about HTTP or storage.

The application BookingService orchestrates: create the entity, check capacity, save, build a result DTO.

The infrastructure implements the repository interface declared by the domain (dependency inversion).

The presentation layer maps results to HTTP status codes.

Validation lives in two places on purpose: format/required checks at the edge, business invariants in the domain.

flowchart TD
  P[Presentation: handleHttp] --> A[Application: BookingService]
  A --> D[Domain: Reservation, ReservationRepository]
  I[Infrastructure: InMemoryReservations] -. implements .-> D

Common Mistakes

Business rules in controllers or in SQL stored procedures scattered across layers.

Domain depending on infrastructure (entities with ORM/HTTP annotations everywhere, new SqlConnection in entities).

Layer skipping (presentation calling the database directly).

Pass-through layers that add nothing (each layer just forwards calls) — merge them.

Trade-offs and Alternatives

Layers add mapping code (DTO ↔ entity) and indirection; for simple CRUD apps a thin "transaction script" design may be enough. Vertical slices (feature folders) organise by feature instead of by layer and are often combined with layering inside each slice.

Real-World Use

Most enterprise web applications (ASP.NET, Spring, Django), desktop apps (MVVM is a presentation-layer pattern), and embedded stacks (application / service / HAL / drivers — AUTOSAR's layered architecture).

Practice

Q1. (Easy) Which layer contains business rules?

Answer: The domain layer.

Q2. (Medium) Why does the repository interface live in the domain?

Answer: So the domain depends only on its own abstraction; infrastructure implements it (dependency inversion), keeping the domain independent of persistence technology.

Q3. (Advanced) Where should "email must be unique" be enforced?

Answer: It's a business rule spanning many entities: check it in the application service using a repository query (for good error messages) *and* enforce it with a unique constraint in the database (for correctness under concurrency).

Interview Question

Q1. "What goes in the application layer vs the domain layer?"

Answer: Domain: business concepts and rules that would exist even without software — entities, value objects, invariants, domain services, domain events. Application: use-case orchestration — loading aggregates from repositories, calling domain methods, transactions, authorization, sending notifications, mapping DTOs. A test: if it mentions HTTP, transactions or email, it's not domain; if it's a business rule a domain expert would recognise, it shouldn't be only in the application layer.

Quick Quiz

Q1. Name the four layers.

Answer: Presentation, application, domain, infrastructure.

Q2. What do DTOs do between layers?

Answer: Carry data across boundaries without exposing domain objects.

Q3. What is layer skipping?

Answer: A layer bypassing the next one (e.g. UI calling the database directly).

Summary

Separate presentation, application, domain and infrastructure; point dependencies at the domain.

Map DTOs at boundaries; keep business rules in the domain.

Related: Modules 21, 72, 73.