# Layered Architecture Pattern

## Problem

Code responsibilities are mixed together — UI logic calls the database directly, business rules are scattered across handlers, and data access is duplicated everywhere. Changes in one area cascade unpredictably into others. There is no clear separation of concerns.

## Solution

**Layered Architecture** organizes code into horizontal layers, where each layer has a specific responsibility and only communicates with the layer directly below it:

1. **Presentation Layer** — UI, API controllers, request/response handling.
2. **Business Logic Layer** — Domain rules, validation, orchestration.
3. **Data Access Layer** — Persistence, external service calls, data retrieval.
4. **(Optional) Infrastructure Layer** — Cross-cutting concerns (logging, config).

Each layer depends only on the layer beneath it. The lower layers have no knowledge of the layers above.

## When to Use

- Traditional enterprise applications with clear separation of concerns.
- Teams organized by technical function (frontend, backend, database).
- Applications where each layer might be deployed or scaled independently.
- When you want a simple, well-understood architecture for moderate complexity.

## When to Avoid

- Microservices or highly distributed systems (vertical slices often work better).
- Applications where strict layering causes excessive data mapping.
- Simple scripts or small tools where layers add unnecessary overhead.
- When performance-critical paths need to bypass layer boundaries.

## Pseudocode

```
// Data Access Layer
CLASS UserDAO:
    METHOD find_by_email(email) -> user_row
    METHOD save(user_row)

// Business Logic Layer
CLASS UserService:
    CONSTRUCTOR(dao: UserDAO)
    METHOD register(name, email):
        VALIDATE email is unique (via dao)
        user = create_user(name, email)
        dao.save(user)
        RETURN user

// Presentation Layer
CLASS UserHandler:
    CONSTRUCTOR(service: UserService)
    METHOD handle_register(request):
        result = service.register(request.name, request.email)
        RETURN format_response(result)
```

## Python

```python
from dataclasses import dataclass


# =============================================
# DATA ACCESS LAYER
# =============================================

@dataclass
class BookRow:
    id: int
    title: str
    author: str
    available: bool


class BookDAO:
    def __init__(self) -> None:
        self._store: dict[int, BookRow] = {}
        self._next_id = 1

    def save(self, title: str, author: str) -> BookRow:
        row = BookRow(self._next_id, title, author, True)
        self._store[row.id] = row
        self._next_id += 1
        print(f"      [DAO] Saved: {row}")
        return row

    def find_by_id(self, book_id: int) -> BookRow | None:
        return self._store.get(book_id)

    def find_all(self) -> list[BookRow]:
        return list(self._store.values())

    def update(self, row: BookRow) -> None:
        self._store[row.id] = row
        print(f"      [DAO] Updated: {row}")


# =============================================
# BUSINESS LOGIC LAYER
# =============================================

class LibraryService:
    def __init__(self, dao: BookDAO) -> None:
        self._dao = dao

    def add_book(self, title: str, author: str) -> dict:
        print(f"    [Service] Adding book: '{title}' by {author}")
        row = self._dao.save(title, author)
        return {"id": row.id, "title": row.title, "author": row.author}

    def borrow_book(self, book_id: int) -> dict:
        print(f"    [Service] Borrowing book #{book_id}")
        row = self._dao.find_by_id(book_id)
        if not row:
            return {"success": False, "error": "Book not found"}
        if not row.available:
            return {"success": False, "error": "Book already borrowed"}
        row.available = False
        self._dao.update(row)
        return {"success": True, "book": row.title}

    def return_book(self, book_id: int) -> dict:
        print(f"    [Service] Returning book #{book_id}")
        row = self._dao.find_by_id(book_id)
        if not row:
            return {"success": False, "error": "Book not found"}
        if row.available:
            return {"success": False, "error": "Book was not borrowed"}
        row.available = True
        self._dao.update(row)
        return {"success": True, "book": row.title}

    def list_available(self) -> list[dict]:
        return [
            {"id": r.id, "title": r.title, "author": r.author}
            for r in self._dao.find_all()
            if r.available
        ]


# =============================================
# PRESENTATION LAYER
# =============================================

class LibraryHandler:
    def __init__(self, service: LibraryService) -> None:
        self._service = service

    def handle_add(self, title: str, author: str) -> None:
        print(f"  [Handler] Request: Add book")
        result = self._service.add_book(title, author)
        print(f"  [Handler] Response: Added '{result['title']}' (id={result['id']})")

    def handle_borrow(self, book_id: int) -> None:
        print(f"  [Handler] Request: Borrow book #{book_id}")
        result = self._service.borrow_book(book_id)
        if result["success"]:
            print(f"  [Handler] Response: Borrowed '{result['book']}'")
        else:
            print(f"  [Handler] Response: Error - {result['error']}")

    def handle_return(self, book_id: int) -> None:
        print(f"  [Handler] Request: Return book #{book_id}")
        result = self._service.return_book(book_id)
        if result["success"]:
            print(f"  [Handler] Response: Returned '{result['book']}'")
        else:
            print(f"  [Handler] Response: Error - {result['error']}")

    def handle_list(self) -> None:
        print(f"  [Handler] Request: List available books")
        books = self._service.list_available()
        if not books:
            print(f"  [Handler] Response: No books available")
        for b in books:
            print(f"  [Handler] Response: #{b['id']} '{b['title']}' by {b['author']}")


def main() -> None:
    # Wire layers
    dao = BookDAO()
    service = LibraryService(dao)
    handler = LibraryHandler(service)

    print("--- Add books ---")
    handler.handle_add("Clean Code", "Robert C. Martin")
    handler.handle_add("Design Patterns", "Gang of Four")
    handler.handle_add("Refactoring", "Martin Fowler")

    print("\n--- List available ---")
    handler.handle_list()

    print("\n--- Borrow book #2 ---")
    handler.handle_borrow(2)

    print("\n--- Try borrowing #2 again ---")
    handler.handle_borrow(2)

    print("\n--- List available ---")
    handler.handle_list()

    print("\n--- Return book #2 ---")
    handler.handle_return(2)

    print("\n--- List available ---")
    handler.handle_list()


if __name__ == "__main__":
    main()
```

**Output:**
```
--- Add books ---
  [Handler] Request: Add book
    [Service] Adding book: 'Clean Code' by Robert C. Martin
      [DAO] Saved: BookRow(id=1, title='Clean Code', author='Robert C. Martin', available=True)
  [Handler] Response: Added 'Clean Code' (id=1)
  [Handler] Request: Add book
    [Service] Adding book: 'Design Patterns' by Gang of Four
      [DAO] Saved: BookRow(id=2, title='Design Patterns', author='Gang of Four', available=True)
  [Handler] Response: Added 'Design Patterns' (id=2)
  [Handler] Request: Add book
    [Service] Adding book: 'Refactoring' by Martin Fowler
      [DAO] Saved: BookRow(id=3, title='Refactoring', author='Martin Fowler', available=True)
  [Handler] Response: Added 'Refactoring' (id=3)

--- List available ---
  [Handler] Request: List available books
  [Handler] Response: #1 'Clean Code' by Robert C. Martin
  [Handler] Response: #2 'Design Patterns' by Gang of Four
  [Handler] Response: #3 'Refactoring' by Martin Fowler

--- Borrow book #2 ---
  [Handler] Request: Borrow book #2
    [Service] Borrowing book #2
      [DAO] Updated: BookRow(id=2, title='Design Patterns', author='Gang of Four', available=False)
  [Handler] Response: Borrowed 'Design Patterns'

--- Try borrowing #2 again ---
  [Handler] Request: Borrow book #2
    [Service] Borrowing book #2
  [Handler] Response: Error - Book already borrowed

--- List available ---
  [Handler] Request: List available books
  [Handler] Response: #1 'Clean Code' by Robert C. Martin
  [Handler] Response: #3 'Refactoring' by Martin Fowler

--- Return book #2 ---
  [Handler] Request: Return book #2
    [Service] Returning book #2
      [DAO] Updated: BookRow(id=2, title='Design Patterns', author='Gang of Four', available=True)
  [Handler] Response: Returned 'Design Patterns'

--- List available ---
  [Handler] Request: List available books
  [Handler] Response: #1 'Clean Code' by Robert C. Martin
  [Handler] Response: #2 'Design Patterns' by Gang of Four
  [Handler] Response: #3 'Refactoring' by Martin Fowler
```

## Go

```go
package main

import "fmt"

// =============================================
// DATA ACCESS LAYER
// =============================================

type BookRow struct {
	ID        int
	Title     string
	Author    string
	Available bool
}

type BookDAO struct {
	store  map[int]BookRow
	nextID int
}

func NewBookDAO() *BookDAO {
	return &BookDAO{store: make(map[int]BookRow), nextID: 1}
}

func (d *BookDAO) Save(title, author string) BookRow {
	row := BookRow{d.nextID, title, author, true}
	d.store[row.ID] = row
	d.nextID++
	fmt.Printf("      [DAO] Saved: #%d '%s' by %s\n", row.ID, row.Title, row.Author)
	return row
}

func (d *BookDAO) FindByID(id int) (BookRow, bool) {
	row, ok := d.store[id]
	return row, ok
}

func (d *BookDAO) FindAll() []BookRow {
	rows := make([]BookRow, 0, len(d.store))
	for _, r := range d.store {
		rows = append(rows, r)
	}
	return rows
}

func (d *BookDAO) Update(row BookRow) {
	d.store[row.ID] = row
	fmt.Printf("      [DAO] Updated: #%d available=%v\n", row.ID, row.Available)
}

// =============================================
// BUSINESS LOGIC LAYER
// =============================================

type LibraryService struct {
	dao *BookDAO
}

func NewLibraryService(dao *BookDAO) *LibraryService {
	return &LibraryService{dao: dao}
}

func (s *LibraryService) AddBook(title, author string) BookRow {
	fmt.Printf("    [Service] Adding book: '%s' by %s\n", title, author)
	return s.dao.Save(title, author)
}

func (s *LibraryService) BorrowBook(id int) (string, error) {
	fmt.Printf("    [Service] Borrowing book #%d\n", id)
	row, ok := s.dao.FindByID(id)
	if !ok {
		return "", fmt.Errorf("Book not found")
	}
	if !row.Available {
		return "", fmt.Errorf("Book already borrowed")
	}
	row.Available = false
	s.dao.Update(row)
	return row.Title, nil
}

func (s *LibraryService) ReturnBook(id int) (string, error) {
	fmt.Printf("    [Service] Returning book #%d\n", id)
	row, ok := s.dao.FindByID(id)
	if !ok {
		return "", fmt.Errorf("Book not found")
	}
	if row.Available {
		return "", fmt.Errorf("Book was not borrowed")
	}
	row.Available = true
	s.dao.Update(row)
	return row.Title, nil
}

func (s *LibraryService) ListAvailable() []BookRow {
	var result []BookRow
	for _, r := range s.dao.FindAll() {
		if r.Available {
			result = append(result, r)
		}
	}
	return result
}

// =============================================
// PRESENTATION LAYER
// =============================================

type LibraryHandler struct {
	service *LibraryService
}

func NewLibraryHandler(service *LibraryService) *LibraryHandler {
	return &LibraryHandler{service: service}
}

func (h *LibraryHandler) HandleAdd(title, author string) {
	fmt.Println("  [Handler] Request: Add book")
	row := h.service.AddBook(title, author)
	fmt.Printf("  [Handler] Response: Added '%s' (id=%d)\n", row.Title, row.ID)
}

func (h *LibraryHandler) HandleBorrow(id int) {
	fmt.Printf("  [Handler] Request: Borrow book #%d\n", id)
	title, err := h.service.BorrowBook(id)
	if err != nil {
		fmt.Printf("  [Handler] Response: Error - %s\n", err)
	} else {
		fmt.Printf("  [Handler] Response: Borrowed '%s'\n", title)
	}
}

func (h *LibraryHandler) HandleReturn(id int) {
	fmt.Printf("  [Handler] Request: Return book #%d\n", id)
	title, err := h.service.ReturnBook(id)
	if err != nil {
		fmt.Printf("  [Handler] Response: Error - %s\n", err)
	} else {
		fmt.Printf("  [Handler] Response: Returned '%s'\n", title)
	}
}

func (h *LibraryHandler) HandleList() {
	fmt.Println("  [Handler] Request: List available books")
	books := h.service.ListAvailable()
	if len(books) == 0 {
		fmt.Println("  [Handler] Response: No books available")
		return
	}
	for _, b := range books {
		fmt.Printf("  [Handler] Response: #%d '%s' by %s\n", b.ID, b.Title, b.Author)
	}
}

func main() {
	dao := NewBookDAO()
	service := NewLibraryService(dao)
	handler := NewLibraryHandler(service)

	fmt.Println("--- Add books ---")
	handler.HandleAdd("Clean Code", "Robert C. Martin")
	handler.HandleAdd("Design Patterns", "Gang of Four")
	handler.HandleAdd("Refactoring", "Martin Fowler")

	fmt.Println("\n--- List available ---")
	handler.HandleList()

	fmt.Println("\n--- Borrow book #2 ---")
	handler.HandleBorrow(2)

	fmt.Println("\n--- Try borrowing #2 again ---")
	handler.HandleBorrow(2)

	fmt.Println("\n--- List available ---")
	handler.HandleList()

	fmt.Println("\n--- Return book #2 ---")
	handler.HandleReturn(2)

	fmt.Println("\n--- List available ---")
	handler.HandleList()
}
```

**Output:**
```
--- Add books ---
  [Handler] Request: Add book
    [Service] Adding book: 'Clean Code' by Robert C. Martin
      [DAO] Saved: #1 'Clean Code' by Robert C. Martin
  [Handler] Response: Added 'Clean Code' (id=1)
  [Handler] Request: Add book
    [Service] Adding book: 'Design Patterns' by Gang of Four
      [DAO] Saved: #2 'Design Patterns' by Gang of Four
  [Handler] Response: Added 'Design Patterns' (id=2)
  [Handler] Request: Add book
    [Service] Adding book: 'Refactoring' by Martin Fowler
      [DAO] Saved: #3 'Refactoring' by Martin Fowler
  [Handler] Response: Added 'Refactoring' (id=3)

--- List available ---
  [Handler] Request: List available books
  [Handler] Response: #1 'Clean Code' by Robert C. Martin
  [Handler] Response: #2 'Design Patterns' by Gang of Four
  [Handler] Response: #3 'Refactoring' by Martin Fowler

--- Borrow book #2 ---
  [Handler] Request: Borrow book #2
    [Service] Borrowing book #2
      [DAO] Updated: #2 available=false
  [Handler] Response: Borrowed 'Design Patterns'

--- Try borrowing #2 again ---
  [Handler] Request: Borrow book #2
    [Service] Borrowing book #2
  [Handler] Response: Error - Book already borrowed

--- List available ---
  [Handler] Request: List available books
  [Handler] Response: #1 'Clean Code' by Robert C. Martin
  [Handler] Response: #3 'Refactoring' by Martin Fowler

--- Return book #2 ---
  [Handler] Request: Return book #2
    [Service] Returning book #2
      [DAO] Updated: #2 available=true
  [Handler] Response: Returned 'Design Patterns'

--- List available ---
  [Handler] Request: List available books
  [Handler] Response: #1 'Clean Code' by Robert C. Martin
  [Handler] Response: #2 'Design Patterns' by Gang of Four
  [Handler] Response: #3 'Refactoring' by Martin Fowler
```

## Related Patterns

- **Hexagonal Architecture** — hexagonal architecture (Ports & Adapters) is a more flexible alternative that avoids strict top-down layer dependencies.
- **Clean Architecture** — clean architecture extends layered thinking with an explicit dependency rule: dependencies point inward only.
- **Facade** — each layer can be thought of as a facade over the layers below it, hiding their complexity.
