# Service Layer Pattern

## Problem

Application logic (use cases, workflow orchestration, transaction management) gets tangled with UI code, API handlers, or domain logic. This makes it impossible to reuse the same business workflows from different entry points (web, CLI, message queue) and muddies the separation of concerns.

## Solution

Introduce a **Service Layer** that defines the application's boundary. It exposes a set of available operations (use cases) and coordinates the application's response in each operation — delegating domain logic to domain objects, handling transactions, and translating between external and internal representations.

## When to Use

- Multiple interfaces (REST API, CLI, message consumer) need the same business operations.
- You want a clear transaction boundary for each use case.
- The application has non-trivial orchestration logic (multi-step workflows).
- You want to keep controllers/handlers thin.

## When to Avoid

- Trivial CRUD apps where the service layer would just proxy to the repository.
- When your framework already provides sufficient use-case orchestration.
- Extremely simple scripts or single-entry-point tools.

## Pseudocode

```
CLASS OrderService:
    CONSTRUCTOR(order_repo, payment_gateway, notifier):
        STORE dependencies

    METHOD place_order(customer_id, items):
        order = Order.create(customer_id, items)
        order_repo.add(order)
        payment_gateway.charge(customer_id, order.total)
        notifier.send_confirmation(customer_id, order.id)
        RETURN order.id
```

## Python

```python
from dataclasses import dataclass, field


@dataclass
class OrderItem:
    name: str
    price: float
    quantity: int


@dataclass
class Order:
    id: int
    customer_id: int
    items: list[OrderItem] = field(default_factory=list)

    @property
    def total(self) -> float:
        return sum(i.price * i.quantity for i in self.items)


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

    def add(self, order: Order) -> None:
        order.id = self._next_id
        self._next_id += 1
        self._store[order.id] = order
        print(f"    [Repo] Saved order #{order.id}")

    def get(self, order_id: int) -> Order | None:
        return self._store.get(order_id)


class PaymentGateway:
    def charge(self, customer_id: int, amount: float) -> None:
        print(f"    [Payment] Charged customer {customer_id}: ${amount:.2f}")


class Notifier:
    def send_confirmation(self, customer_id: int, order_id: int) -> None:
        print(f"    [Notifier] Sent confirmation to customer {customer_id} for order #{order_id}")


class OrderService:
    """Service layer: orchestrates the place-order use case."""

    def __init__(self, repo: OrderRepository, payment: PaymentGateway, notifier: Notifier) -> None:
        self._repo = repo
        self._payment = payment
        self._notifier = notifier

    def place_order(self, customer_id: int, items: list[OrderItem]) -> int:
        print(f"  [Service] Placing order for customer {customer_id}...")
        order = Order(id=0, customer_id=customer_id, items=items)
        self._repo.add(order)
        self._payment.charge(customer_id, order.total)
        self._notifier.send_confirmation(customer_id, order.id)
        print(f"  [Service] Order #{order.id} placed. Total: ${order.total:.2f}")
        return order.id

    def get_order(self, order_id: int) -> Order | None:
        return self._repo.get(order_id)


def main() -> None:
    repo = OrderRepository()
    payment = PaymentGateway()
    notifier = Notifier()
    service = OrderService(repo, payment, notifier)

    print("--- Place order 1 ---")
    oid1 = service.place_order(42, [
        OrderItem("Widget", 9.99, 2),
        OrderItem("Gadget", 24.99, 1),
    ])

    print("\n--- Place order 2 ---")
    oid2 = service.place_order(7, [
        OrderItem("Doohickey", 4.99, 5),
    ])

    print("\n--- Retrieve orders ---")
    for oid in [oid1, oid2]:
        order = service.get_order(oid)
        if order:
            print(f"  Order #{order.id}: customer={order.customer_id}, total=${order.total:.2f}")


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

**Output:**
```
--- Place order 1 ---
  [Service] Placing order for customer 42...
    [Repo] Saved order #1
    [Payment] Charged customer 42: $44.97
    [Notifier] Sent confirmation to customer 42 for order #1
  [Service] Order #1 placed. Total: $44.97

--- Place order 2 ---
  [Service] Placing order for customer 7...
    [Repo] Saved order #2
    [Payment] Charged customer 7: $24.95
    [Notifier] Sent confirmation to customer 7 for order #2
  [Service] Order #2 placed. Total: $24.95

--- Retrieve orders ---
  Order #1: customer=42, total=$44.97
  Order #2: customer=7, total=$24.95
```

## JavaScript

```javascript
class OrderItem {
  constructor(name, price, quantity) {
    this.name = name;
    this.price = price;
    this.quantity = quantity;
  }
}

class Order {
  constructor(id, customerId, items = []) {
    this.id = id;
    this.customerId = customerId;
    this.items = items;
  }

  get total() {
    return this.items.reduce((sum, i) => sum + i.price * i.quantity, 0);
  }
}

class OrderRepository {
  constructor() {
    this._store = new Map();
    this._nextId = 1;
  }

  add(order) {
    order.id = this._nextId++;
    this._store.set(order.id, order);
    console.log(`    [Repo] Saved order #${order.id}`);
  }

  get(orderId) {
    return this._store.get(orderId) || null;
  }
}

class PaymentGateway {
  charge(customerId, amount) {
    console.log(`    [Payment] Charged customer ${customerId}: $${amount.toFixed(2)}`);
  }
}

class Notifier {
  sendConfirmation(customerId, orderId) {
    console.log(`    [Notifier] Sent confirmation to customer ${customerId} for order #${orderId}`);
  }
}

class OrderService {
  constructor(repo, payment, notifier) {
    this._repo = repo;
    this._payment = payment;
    this._notifier = notifier;
  }

  placeOrder(customerId, items) {
    console.log(`  [Service] Placing order for customer ${customerId}...`);
    const order = new Order(0, customerId, items);
    this._repo.add(order);
    this._payment.charge(customerId, order.total);
    this._notifier.sendConfirmation(customerId, order.id);
    console.log(`  [Service] Order #${order.id} placed. Total: $${order.total.toFixed(2)}`);
    return order.id;
  }

  getOrder(orderId) {
    return this._repo.get(orderId);
  }
}

// --- Main ---
const repo = new OrderRepository();
const payment = new PaymentGateway();
const notifier = new Notifier();
const service = new OrderService(repo, payment, notifier);

console.log("--- Place order 1 ---");
const oid1 = service.placeOrder(42, [
  new OrderItem("Widget", 9.99, 2),
  new OrderItem("Gadget", 24.99, 1),
]);

console.log("\n--- Place order 2 ---");
const oid2 = service.placeOrder(7, [
  new OrderItem("Doohickey", 4.99, 5),
]);

console.log("\n--- Retrieve orders ---");
for (const oid of [oid1, oid2]) {
  const order = service.getOrder(oid);
  if (order) {
    console.log(`  Order #${order.id}: customer=${order.customerId}, total=$${order.total.toFixed(2)}`);
  }
}
```

**Output:**
```
--- Place order 1 ---
  [Service] Placing order for customer 42...
    [Repo] Saved order #1
    [Payment] Charged customer 42: $44.97
    [Notifier] Sent confirmation to customer 42 for order #1
  [Service] Order #1 placed. Total: $44.97

--- Place order 2 ---
  [Service] Placing order for customer 7...
    [Repo] Saved order #2
    [Payment] Charged customer 7: $24.95
    [Notifier] Sent confirmation to customer 7 for order #2
  [Service] Order #2 placed. Total: $24.95

--- Retrieve orders ---
  Order #1: customer=42, total=$44.97
  Order #2: customer=7, total=$24.95
```

## Related Patterns

- **Repository** — the service layer delegates data access to repositories, keeping persistence concerns out of business logic.
- **Unit of Work** — use unit of work inside the service layer to coordinate transactional boundaries across multiple repository operations.
- **Facade** — the service layer acts as a facade over the domain model, exposing use cases as a simplified API.
- **Command** — command objects can represent the service layer's operations, enabling queuing, logging, and undo.
