# Services and Repositories

This topic assumes familiarity with `backend/frameworks/05-controllers` (thin controllers delegate outward) and `backend/frameworks/09-dependency_injection` (how the objects described here actually get wired together). It also connects to `architecture/architecture/07-repository_pattern`, which covers the repository abstraction as a general design pattern — this topic is about how that pattern plugs specifically into a framework's controller/service layering, not the pattern's design rationale in the abstract.

## 1. Three Layers, Three Jobs

A typical framework-backed backend splits request handling into three layers, each with exactly one job:

```
Controller  -> parse the request, call a service, shape the response  (backend/frameworks/05-controllers)
Service     -> business logic: rules, orchestration, decisions
Repository  -> data access: talk to the database (or any other storage), nothing else
```

```ts
@Controller("orders")
class OrderController {
  constructor(private orderService: OrderService) {}

  @Post()
  async create(@Body() dto: CreateOrderDto) {
    return this.orderService.placeOrder(dto); // controller does NOT touch the database
  }
}

@Injectable()
class OrderService {
  constructor(private orderRepo: OrderRepository, private inventoryRepo: InventoryRepository) {}

  async placeOrder(dto: CreateOrderDto) {
    const inStock = await this.inventoryRepo.checkStock(dto.productId, dto.quantity);
    if (!inStock) throw new BadRequestException("insufficient stock"); // BUSINESS RULE lives here
    return this.orderRepo.create(dto); // delegates the actual write
  }
}

@Injectable()
class OrderRepository {
  constructor(private db: Database) {}
  create(dto: CreateOrderDto) {
    return this.db.query("INSERT INTO orders ...", [dto]); // ONLY data access, no business rules
  }
}
```

Each arrow flows one direction: controller calls service, service calls repository — a repository never calls back up into a service, and a controller never talks to a repository directly.

![Controller, service, repository and database layers with dependencies pointing one way and the repository returning domain objects rather than raw rows](diagrams/01-three-layers.svg)

## 2. Why the Service Layer Exists at All

It's tempting to ask why `OrderController` can't just call `OrderRepository` directly and skip the middle layer — for a single trivial CRUD operation, it could. The service layer earns its place the moment there's a *decision* to make that isn't itself a database query: checking stock before allowing an order, applying a discount rule, coordinating a write across two repositories (orders and inventory) as one logical operation, sending a notification after a successful write. None of that is "get/set a row" — it's business logic, and business logic belongs in the service, not smeared across the controller (which should stay a thin request/response adapter) or the repository (which should stay ignorant of *why* a query runs, only *how*).

## 3. Why the Repository Layer Exists at All

Symmetrically, the repository exists to isolate *how data is stored* from *what the business logic needs to do with it*. `OrderService` calls `orderRepo.create(dto)` without knowing or caring whether that's a raw SQL `INSERT`, an ORM call, a call to a third-party API, or (in a test) an in-memory array push:

```ts
class InMemoryOrderRepository implements OrderRepository {
  private orders: Order[] = [];
  create(dto: CreateOrderDto) {
    const order = { id: this.orders.length + 1, ...dto };
    this.orders.push(order);
    return order;
  }
}
```

This is the same substitutability payoff covered in `backend/frameworks/09-dependency_injection` — because `OrderService` depends on the repository's *interface*, not a concrete database-backed class, a test can construct `OrderService` with `InMemoryOrderRepository` and exercise the real business logic (the stock check, the discount rule) with zero real I/O.

## 4. What Belongs Where — A Concrete Sorting Exercise

| Concern | Layer |
|---|---|
| Parsing `req.body`, setting the HTTP status code | Controller |
| "an order needs items and a valid customer" | Service (business rule) |
| "insert this row, return the generated id" | Repository (mechanics) |
| "apply a 10% discount for orders over $100" | Service (business rule) |
| "run this exact SQL join across three tables" | Repository (mechanics) |
| "reject the order if any line item is out of stock" | Service (orchestrates repo calls + decides) |

The dividing line is always the same question: is this a *decision about what should happen*, or a *mechanical detail of how data gets read or written*? The former is a service's job; the latter is a repository's.

## 5. Repositories Return Domain Data, Not Raw Storage Shapes

A well-designed repository hides its storage mechanics behind method names that describe *what* is being asked for, not *how* it's fetched — `findActiveCustomers()`, not `runQuery(sql)`. This is what actually makes swapping the underlying storage possible later (moving from a SQL database to a different store) without touching every service that calls the repository: the service-facing contract is a set of meaningful method names, never a leaked SQL string or an ORM-specific query builder object.

## Common Pitfalls

1. **Putting business logic in the repository.** A repository method that decides "skip this row if the discount would make the total negative" has smuggled a business rule into the data-access layer, which means that rule can never be tested without a real (or in-memory) storage backend, defeating the whole reason a service layer exists.
2. **Putting data-access details in the service.** A service that builds raw SQL strings or ORM query objects directly has skipped the repository entirely, coupling business logic to one specific storage technology.
3. **A fat controller that calls the repository directly, "just this once."** Every exception erodes the "controllers never touch repositories" rule, and the business logic that belonged in a service ends up duplicated the next time a second controller needs the same operation.
4. **A repository interface shaped exactly like a SQL table.** If every method maps 1:1 to a raw column, the abstraction isn't actually hiding storage details — it's just SQL with extra steps, and swapping storage mechanisms later means rewriting every consumer anyway.

## Quick Reference

```
Controller  -> HTTP in/out only: parse request, call service, shape response
Service     -> business logic: rules, decisions, orchestration across repositories
Repository  -> data access only: how storage works, hidden behind meaningful method names
Flow        -> Controller -> Service -> Repository (never backwards, never skipped)
Payoff      -> swap a repository for an in-memory fake in tests without touching the service
```
