Skip to content
Low Level Design Mastery Logo
LowLevelDesign Mastery

Layered Architecture

Organizing complexity through well-defined layers and dependencies

Imagine building a cake with distinct layers:

  • Top layer: Frosting and decorations (what people see)
  • Middle layer: Cake itself (the substance)
  • Bottom layer: The plate (what holds everything)

Each layer has a specific job, and you build from bottom to top. That’s layered architecture - organizing software into horizontal layers where each layer has a specific responsibility!


Diagram

Responsibility: Interface with the outside world

Contains:

  • Web controllers
  • REST API endpoints
  • GraphQL resolvers
  • Views/templates
  • Request/response DTOs
  • Input validation

What it does:

  • Accept user input
  • Display information
  • Handle HTTP/request-response cycle
  • Convert between external formats and application models

Key Point: Application layer orchestrates - coordinates between layers!

Key Point: Application layer orchestrates, domain layer decides!

Responsibility: Core business logic and rules

Contains:

  • Entities
  • Value objects
  • Domain services
  • Domain events
  • Business rules
  • Invariants

What it does:

  • Enforce business rules
  • Maintain consistency
  • Express business concepts
  • Pure business logic (no infrastructure!)

Key Point: Domain layer is pure - no dependencies on infrastructure!

4. Infrastructure Layer (Technical Details)

Section titled “4. Infrastructure Layer (Technical Details)”

Responsibility: Technical implementation details

Contains:

  • Database access (repositories)
  • External API clients
  • File system access
  • Message queue clients
  • Email/SMS sending
  • Caching
  • Logging

What it does:

  • Implement interfaces defined by domain/application layers
  • Handle persistence
  • Integrate with external systems
  • Technical concerns (caching, logging, etc.)

Key Point: Infrastructure implements interfaces, doesn’t define them!


Dependencies point INWARD (toward domain):

Diagram

Domain defines interfaces, infrastructure implements:

# Domain layer defines what it needs
class OrderRepository(ABC):
"""Domain interface - no implementation details!"""
@abstractmethod
async def save(self, order: Order):
pass
@abstractmethod
async def find_by_id(self, order_id: str) -> Optional[Order]:
pass
# Infrastructure layer provides implementation
class PostgresOrderRepository(OrderRepository):
"""Infrastructure implements domain interface"""
async def save(self, order: Order):
# PostgreSQL-specific code
pass
# Application layer uses abstraction
class OrderService:
def __init__(self, order_repo: OrderRepository): # Depends on interface
self._repo = order_repo

Separation of Concerns

Each layer has clear responsibility. UI doesn’t know about database, business logic doesn’t know about HTTP.

Testability

Test domain logic without database. Test application logic without HTTP. Mock dependencies easily.

Maintainability

Changes localized to specific layers. Replace database without touching business logic.

Team Organization

Different teams can own different layers. Frontend team owns presentation, backend owns domain/application.


Problem: Domain layer becomes just data containers, all logic in application layer

# BAD: Anemic domain model
class Order:
"""Just data, no behavior"""
id: str
customer_id: str
items: list
total: Decimal
class OrderService:
"""All logic in application service"""
def calculate_total(self, order):
return sum(item.price * item.quantity for item in order.items)
def can_be_cancelled(self, order):
return order.status in ["draft", "confirmed"]
# GOOD: Rich domain model
class Order:
"""Data + behavior"""
def calculate_total(self) -> Decimal:
return sum(line.subtotal() for line in self.lines)
def cancel(self):
if not self._can_be_cancelled():
raise ValueError("Order cannot be cancelled")
self.status = OrderStatus.CANCELLED
def _can_be_cancelled(self) -> bool:
return self.status in [OrderStatus.DRAFT, OrderStatus.CONFIRMED]

Problem: Abstractions leak between layers

# BAD: Infrastructure leaking to presentation
@app.get("/orders/{order_id}")
async def get_order(order_id: str, db: Session): # Database session in controller!
order = db.query(OrderModel).filter_by(id=order_id).first()
return order
# GOOD: Proper layering
@app.get("/orders/{order_id}")
async def get_order(order_id: str):
order = await order_service.get_order(order_id) # Use application layer
return OrderResponse.from_domain(order)

Problem: Skipping layers creates unnecessary code

# Sometimes OK to skip application layer for simple queries
@app.get("/orders/{order_id}")
async def get_order(order_id: str):
# Simple query - can go directly to repository
order = await order_repository.find_by_id(order_id)
return OrderResponse.from_domain(order)
# Use application layer for complex operations
@app.post("/orders")
async def create_order(request: CreateOrderRequest):
# Complex workflow - must use application layer
order = await order_service.place_order(...)
return OrderResponse.from_domain(order)

  1. Building Traditional Enterprise Applications

    • Clear separation between UI, business logic, database
    • Well-understood requirements
    • Team familiar with pattern
  2. Domain Logic is Complex

    • Need to isolate business rules
    • Many business invariants to maintain
    • Domain experts involved
  3. Long-Lived Applications

    • Expect to maintain for years
    • Technology will change (replace database, UI framework)
    • Team members will change
  4. Monolithic Applications

    • Single deployment unit
    • Shared database
    • Clear layer boundaries
  1. Microservices

    • Each service is small enough that layers add overhead
    • Consider vertical slices instead
  2. Simple CRUD Applications

    • No complex business logic
    • Just reading/writing data
    • Layers add unnecessary complexity
  3. Event-Driven Systems

    • Consider hexagonal architecture instead
    • More flexible for async communication

Dependencies Flow Inward

Always depend on inner layers. Domain never depends on infrastructure or presentation!

Rich Domain Models

Put business logic in domain entities, not in services. Avoid anemic domain models.

Interface Segregation

Domain defines interfaces, infrastructure implements. Use dependency inversion!

Pragmatic Layering

Follow the spirit, not the letter. Skip layers when it makes sense for simple operations.



  • “Domain-Driven Design” by Eric Evans - Rich domain models
  • “Clean Architecture” by Robert C. Martin - Modern layering
  • “Patterns of Enterprise Application Architecture” by Martin Fowler
  • “Implementing Domain-Driven Design” by Vaughn Vernon