ArchitectMind
  1. Home
  2. Tutorials
  3. Book 5 · Software Design & Design Patterns
  4. Volume V
  5. Layered Architecture
Book 5 · Volume V — Enterprise & Application Design Patterns · Topic 1.1

Layered Architecture

Presentation, application, domain and infrastructure

7 min readStage 2 · Frameworks & design8 interview questions
Chapter
Chapter 1 — Enterprise Architecture Patterns
Prerequisites
Volume II (SRP, DIP); Volume III Chapter 8 (Facade)
You will be able to
Organise an application into presentation, application, domain and infrastructure layers, apply the dependency rule (and its inverted, hexagonal form), trace a request through the layers, and enforce layering automatically
Covers
Presentation; Application; Domain; Infrastructure
On this page
  1. 1. Concept / Definition
  2. 2. Why It Exists
  3. 3. Core Concepts
  4. 4. How It Works Internally — Dependency Direction
  5. 4A. Deep Dive — Enforcing Layers Automatically
  6. 5. APIs / Features / Syntax
  7. 6. Practical Examples
  8. 7. Common Problems & Mistakes
  9. 8. Performance & Production Considerations
  10. 9. Architecture & Design Considerations
  11. 10. Interview Questions & Answers

1Concept / Definition

A layered architecture organises an application into horizontal layers, each with a distinct kind of responsibility, where a layer uses the services of the layer(s) below it and is unaware of those above.

Four layers of an enterprise application
Figure 1 — Four layers of an enterprise application
LayerResponsibilityTypical Spring components
PresentationAccept input, render output, protocol concerns@RestController, @KafkaListener, request/response DTOs
ApplicationUse cases, orchestration, transactions, security@Service application services, commands, events
DomainBusiness concepts, rules and invariantsEntities, value objects, aggregates, domain services
InfrastructureTechnical capabilities and external systemsRepositories (JPA), HTTP clients, messaging, files
Table 1 — The four layers

2Why It Exists

Without layers, a controller computes discounts with SQL, an entity sends emails, and a scheduled job copies half a service's logic. Every change touches unrelated concerns and nothing can be tested in isolation. Layering (popularised for enterprise systems by Fowler and Evans) separates concerns so that each can change for its own reasons: a new API version touches presentation, a new rule touches the domain, a database migration touches infrastructure.

3Core Concepts

3.1 Presentation

The presentation layer adapts external protocols (HTTP, messaging, CLI, UI) to the application.

JAVA
@RestController
@RequestMapping("/api/v1/orders")
class OrderController {
    private final OrderService orders;                     // application layer only
    OrderController(OrderService orders) { this.orders = orders; }
 
    @PostMapping
    ResponseEntity<OrderResponse> place(@Valid @RequestBody PlaceOrderRequest req) {
        OrderId id = orders.place(req.toCommand());         // DTO → command
        return ResponseEntity.created(URI.create("/api/v1/orders/" + id.value()))
                             .body(orders.view(id));        // result DTO
    }
}
Belongs in presentationDoes not belong
Request parsing, validation of shape (@Valid)Business rules
HTTP status codes, headers, content negotiationTransactions
Mapping requests to commandsDirect repository access
Authentication integration (principal extraction)SQL, entity manipulation
Table 2 — Presentation scope

3.2 Application

The application layer implements use cases: it coordinates domain objects and infrastructure, owns transaction boundaries and applies application-level policies (authorisation, idempotency, events). It is thin — the service layer (Topic 1.2).

JAVA
@Service
public class OrderService {
    private final OrderRepository orders;
    private final CustomerRepository customers;
    private final ApplicationEventPublisher events;
 
    @Transactional
    @PreAuthorize("hasRole('CUSTOMER')")
    public OrderId place(PlaceOrder cmd) {
        Customer c = customers.get(cmd.customerId());
        Order order = Order.place(c, cmd.lines());           // domain decides
        orders.save(order);
        order.pullEvents().forEach(events::publishEvent);
        return order.id();
    }
}

3.3 Domain

The domain layer holds the business model: entities, value objects, aggregates, domain services and domain events. It expresses what the business means and enforces invariants.

JAVA
public class Order {                            // domain entity (aggregate root)
    private final OrderId id;
    private final CustomerId customer;
    private final List<OrderLine> lines = new ArrayList<>();
    private OrderStatus status = OrderStatus.PLACED;
 
    public static Order place(Customer customer, List<LineRequest> requested) {
        if (customer.isBlocked()) throw new CustomerBlocked(customer.id());
        if (requested.isEmpty() || requested.size() > 50) {
            throw new InvalidOrderSize(requested.size());
        }
        var order = new Order(OrderId.newId(), customer.id());
        requested.forEach(r -> order.lines.add(OrderLine.of(r)));
        order.record(new OrderPlaced(order.id, customer.id(), order.total()));
        return order;
    }
    public Money total() {
        return lines.stream().map(OrderLine::subtotal).reduce(Money.ZERO, Money::plus);
    }
}
Domain layer ruleReason
No Spring web, no HTTP, no messaging APIsRules independent of delivery
Persistence annotations minimal or absentModel shaped by business, not schema (Topic 2.4)
No calls to external systemsUse ports/gateways implemented in infrastructure
Rich behaviour, not just dataAvoid anemic model (Chapter 2)
Table 3 — Domain layer rules

3.4 Infrastructure

Infrastructure provides technical capabilities: persistence, messaging, external APIs, file storage, email.

JAVA
@Repository
class JpaOrderRepository implements OrderRepository {          // implements a domain port
    private final SpringDataOrderJpa jpa;
    JpaOrderRepository(SpringDataOrderJpa jpa) { this.jpa = jpa; }
 
    public Order get(OrderId id) {
        return jpa.findById(id.value()).map(OrderEntity::toDomain)
                  .orElseThrow(() -> new OrderNotFound(id));
    }
    public void save(Order order) { jpa.save(OrderEntity.from(order)); }
}

Infrastructure is where technology choices live — and therefore where they can be replaced.

4How It Works Internally — Dependency Direction

Dependency direction: classic vs dependency-inverted
Figure 2 — Dependency direction: classic vs dependency-inverted

In classic layering, each layer depends on the one below, so the domain depends on infrastructure (e.g. JPA repositories). In the dependency-inverted form (hexagonal / ports & adapters / clean architecture), the domain defines ports (interfaces such as OrderRepository) and infrastructure implements them — the domain depends on nothing technical.

FormDomain depends onConsequence
ClassicInfrastructure (JPA, clients)Simpler; domain harder to test and evolve
InvertedNothing technical (own interfaces)Domain testable in memory; infrastructure replaceable
Table 4 — Two dependency rules
A request through the layers
Figure 3 — A request through the layers

Strict vs relaxed layering. Strict: a layer may call only the layer directly below. Relaxed: it may call any lower layer. Most Spring applications use relaxed layering for reads (e.g. query services reading projections) and strict layering for writes (through application services and aggregates).

4ADeep Dive — Enforcing Layers Automatically

Layering violations to catch
Figure 4 — Layering violations to catch

Layering decays without enforcement. ArchUnit expresses the rules as tests:

JAVA
@AnalyzeClasses(packages = "com.shop")
class LayeringTest {
    @ArchTest
    static final ArchRule layers = layeredArchitecture().consideringAllDependencies()
        .layer("Presentation").definedBy("..web..")
        .layer("Application").definedBy("..application..")
        .layer("Domain").definedBy("..domain..")
        .layer("Infrastructure").definedBy("..infrastructure..")
        .whereLayer("Presentation").mayNotBeAccessedByAnyLayer()
        .whereLayer("Application").mayOnlyBeAccessedByLayers("Presentation")
        .whereLayer("Domain").mayOnlyBeAccessedByLayers("Application", "Infrastructure")
        .whereLayer("Infrastructure").mayNotBeAccessedByAnyLayer();
 
    @ArchTest
    static final ArchRule domainIsFrameworkFree = noClasses()
        .that().resideInAPackage("..domain..")
        .should().dependOnClassesThat()
        .resideInAnyPackage("org.springframework.web..", "jakarta.servlet..",
                            "org.apache.kafka..");
}

Layers inside modules. In a modular monolith (Spring Modulith), each business module (orders, payments, catalog) has its own layers; modules communicate through application-layer APIs or events. This "vertical slices of horizontal layers" structure scales better than one global set of layers for a large codebase.

StructurePackage styleScales to
Package by layerweb, service, repository, domainSmall applications
Package by feature/module, layered insideorders.web, orders.application, orders.domain, orders.infrastructureLarge applications, modular monoliths
Table 5 — Package structures

5APIs / Features / Syntax

Tool / featureRole
Packages / JPMS modulesPhysical grouping and visibility
Package-private classesHide infrastructure implementations
Spring stereotypes (@RestController, @Service, @Repository)Document layer intent; @Repository adds exception translation
ArchUnit layeredArchitecture()Enforce dependency rules in tests
Spring Modulith ApplicationModules.verify()Enforce module boundaries
jMolecules annotationsExpress layers/DDD roles explicitly
Table 6 — Tooling

6Practical Examples

6.1 Moving a rule to its layer

A controller checked if (order.getStatus() == SHIPPED) return 409. Moving the rule into Order.cancel() (domain) and mapping OrderAlreadyShipped to 409 in a @RestControllerAdvice (presentation) made the rule apply to the Kafka listener and batch job too.

6.2 Package layout for a module

TEXT
com.shop.orders
 ├─ web            OrderController, PlaceOrderRequest, OrderResponse
 ├─ application    OrderService, PlaceOrder (command), OrderView
 ├─ domain         Order, OrderLine, Money, OrderRepository (port), OrderPlaced
 └─ infrastructure JpaOrderRepository, OrderEntity, PaymentGatewayClient

6.3 Testing per layer

LayerTest style
DomainPlain unit tests, no Spring
ApplicationUnit tests with fake repositories, or @SpringBootTest slices
Presentation@WebMvcTest with mocked application services
Infrastructure@DataJpaTest, Testcontainers, WireMock
Table 7 — Test strategy by layer

7Common Problems & Mistakes

  • Controllers calling repositories directly.
  • Returning JPA entities from controllers.
  • Business rules in controllers or listeners.
  • Domain depending on frameworks (web, messaging).
  • Layers as folders only, without dependency rules.
  • Pass-through layers that add nothing (a service that just forwards to a repository for every method, forever).

8Performance & Production Considerations

AspectGuidance
Call overheadLayer boundaries are method calls — negligible
Mapping costsDTO/entity mapping is cheap; N+1 queries are not (Topic 3.2)
Read pathsAllow query services to read projections directly for heavy reads
TransactionsKeep them in the application layer, short and free of remote calls
Table 8 — Production guidance

9Architecture & Design Considerations

  • Prefer dependency-inverted layering for domains with meaningful rules.
  • Combine modules (vertical) with layers (horizontal) in larger systems.
  • Enforce with ArchUnit/Modulith in CI.
  • Accept relaxed layering for reads where it simplifies without leaking rules.

10Interview Questions & Answers

Q1Basic

What are the four typical layers of an enterprise application?

A typical enterprise application has four layers. The presentation layer handles interaction with the outside world, such as REST controllers, web pages and message listeners. The application layer coordinates use cases, such as placing an order, managing transactions and authorisation and calling the domain. The domain layer contains the business model and rules, such as orders, prices and invariants. The infrastructure layer provides technical capabilities such as persistence, messaging and calls to external systems. Each layer has a clear responsibility, so changes to, for example, the database or the API format stay mostly within one layer.

Q2Basic

What belongs in the presentation layer?

The presentation layer handles everything specific to how the application is called. For a REST API that means controllers, request and response DTOs, mapping between DTOs and application commands or results, validation of the input's shape with Bean Validation, content negotiation and mapping outcomes and exceptions to HTTP status codes and error bodies. Message listeners and scheduled jobs are also presentation-layer entry points. It should not contain business rules or call repositories directly. Keeping it thin means the same use cases can be offered through several channels, and the API can change without touching the domain.

Q3Intermediate

What is the dependency rule in layered architecture?

The dependency rule says that a layer may depend only on layers below it, never on layers above. Presentation depends on application, application on domain, and so on, but the domain never calls a controller or knows about HTTP. In a stricter form, upper layers depend on abstractions that lower layers implement. The rule keeps the core of the system stable: changes in the user interface or in technical details do not ripple into business logic. It also prevents cycles between layers, which would make them impossible to change or test independently. Tools such as ArchUnit can enforce it automatically.

Q4Intermediate

How does hexagonal architecture change classic layering?

In classic layering, the domain sits above infrastructure and depends on it, for example on JPA repositories. Hexagonal architecture inverts that dependency. The domain and application core define ports, which are interfaces describing what they need, such as OrderRepository or PaymentGateway, and infrastructure adapters implement those ports with specific technologies. Inbound adapters, such as controllers, call the core through its use-case ports. The core therefore depends on nothing technical, can be tested without databases or web servers, and can switch technologies by replacing adapters. Layers still exist, but dependencies all point inward towards the domain.

Q5Senior Engineer

Why should controllers not call repositories directly?

A controller that calls a repository directly skips the application layer, where use-case logic lives. Transactions, authorisation checks, business validations and domain events are then either missing or reimplemented in the controller. When the same operation is later exposed through another entry point, such as a message listener or batch job, the rules must be duplicated, and they drift apart. Controllers also become harder to test and grow into large classes mixing HTTP and business logic. Routing every operation through an application service keeps rules in one place and makes the controller a thin translation layer.

Q6Senior Engineer

How do you enforce layering in a Spring project?

Write the rules as automated tests that run in the build. ArchUnit's layeredArchitecture rule can declare the layers by package and state which layers may access which, so any violating import fails the build. Additional ArchUnit rules can forbid framework dependencies in the domain, for example no Spring or JPA annotations in domain packages. Spring Modulith can verify module boundaries and dependencies between application modules and generate documentation from them. Combine these with code review guidelines for new dependencies. Automated checks are essential, because layering erodes gradually through small shortcuts that each look harmless.

Q7Technical Architect

Package by layer or by feature?

For anything beyond a small application, package by feature or module first, and use layers inside each module. A layer-first structure, with top-level packages for controllers, services and repositories, scatters each feature across the whole code base and makes module boundaries invisible. A feature-first structure, such as orders, payments and catalog packages, each containing its own web, application, domain and infrastructure sub-packages, keeps related code together, makes ownership clear and allows package-private visibility to hide internals. It also prepares the code for modularisation tools such as Spring Modulith, and for extracting a module into a separate service later.

Q8Solution Architect

How does layering support evolving a monolith into services?

Good layering makes a monolith much easier to split. If each module exposes its capabilities through an application-layer API, such as an OrderService interface, and its infrastructure dependencies are inverted behind ports, then extracting the module as a service mostly means replacing in-process calls with remote calls, such as REST or messaging, and moving the module's data to its own database. The domain code moves unchanged. Without layering, business logic is tangled with controllers and data access, so extraction requires a rewrite. That is why a well-layered, modular monolith is often the best first step towards microservices.

Newsletter

Get new tutorials every week

New ArchitectMind tutorials and interview questions, once a week. No spam, unsubscribe anytime.

Almost done! Check your inbox and click the confirmation link to complete your subscription.
Keep going in Book 5

Software Design & Design Patterns

Volume I · Topic 1.1

Software Design

Software Design Foundations & Principles

Volume I · Topic 1.2

Why Software Design Matters

Software Design Foundations & Principles

Volume I · Topic 1.3

Good vs Poor Design

Software Design Foundations & Principles

Volume II · Topic 1.1

Objects

Object-Oriented Design & SOLID

Volume II · Topic 1.2

Classes

Object-Oriented Design & SOLID

Volume II · Topic 1.3

Relationships

Object-Oriented Design & SOLID

Volume III · Topic 1.1

What Is a Design Pattern?

Creational & Structural Design Patterns

Volume III · Topic 1.2

Pattern Categories

Creational & Structural Design Patterns

Volume III · Topic 1.3

Pattern vs Framework

Creational & Structural Design Patterns

Volume IV · Topic 1.1

Strategy Pattern

Behavioral Design Patterns

Volume IV · Topic 1.2

Strategy in Java

Behavioral Design Patterns

Volume IV · Topic 1.3

Strategy vs Conditional Logic

Behavioral Design Patterns

Volume V · Topic 1.2

Service Layer

Enterprise & Application Design Patterns

Volume V · Topic 1.3

Repository

Enterprise & Application Design Patterns

Volume VI · Topic 1.1

What Is Refactoring?

Refactoring, Code Quality & Evolutionary Design

Volume VI · Topic 1.2

Why Refactor?

Refactoring, Code Quality & Evolutionary Design

Volume VI · Topic 1.3

Refactoring Safety

Refactoring, Code Quality & Evolutionary Design

Volume VII · Topic 1.1

Method-Level Design

Advanced Design & Architecture-Level Design Decisions

Volume VII · Topic 1.2

Class-Level Design

Advanced Design & Architecture-Level Design Decisions

Volume VII · Topic 1.3

Module-Level Design

Advanced Design & Architecture-Level Design Decisions