Service Layer Pattern

The layer of operations that sits at the edge of an application: one method per use case that checks access, loads domain objects, runs one transaction and fires side effects. Know what belongs in it and what does not.

Overview

A service layer is the set of operations an application offers to the outside world, written as one method per use case: place_order, cancel_subscription, transfer_funds. Every caller (a web controller, a CLI command, a message consumer, a scheduled job) goes through the same method, so the steps of a use case live in one place instead of being copied into each entry point.

The pattern is in Martin Fowler's *Patterns of Enterprise Application Architecture* (2003), where the Service Layer entry was written by Randy Stafford. The catalog page describes it as a layer of services that defines the application's boundary: it sets out the operations clients may call and coordinates the application's response in each one, including control of transactions. The problem it solves is duplication. When a web page, a data loader and an integration gateway all need the same interaction, coding it three times gives three slightly different versions.

Interviewers use the pattern to check whether you can say what a service method coordinates (access checks, loading, the transaction, side effects), what it must not contain (the business rules themselves), and where the transaction starts and ends. Two neighboring topics place this layer in a bigger picture: Layered Architecture stacks it between presentation and data access, and Clean Architecture calls the same thing use cases.

Where the service layer sits

  • Controller or handler — Parse the request, call one service method, shape the response — Business rules, transactions, SQL
  • Service layer — The steps of each use case: check access, load, call the domain, commit, notify — The rules themselves (prices, limits, state changes)
  • Domain objects — The rules: order.mark_paid(), product.reserve(qty) — Database calls, HTTP, email
  • Repositories — Load and save domain objects (see the Repository Pattern topic) — Business rules, commits

What one service method does

Almost every service method follows the same five steps. Saying them in order is the clearest way to show you know the pattern.

  • 1. Authorize — Is this caller allowed to do this? — The user has the buyer role
  • 2. Load — Fetch the domain objects through repositories — Products by SKU
  • 3. Act — Ask the domain objects to change themselves — product.reserve(qty), order.mark_paid(...)
  • 4. Commit — Save everything in one transaction, or roll it all back — The order and the new stock levels
  • 5. Side effects — Do what must happen only after success — Send the confirmation email

The method itself reads like a short script of calls. If you find an if that encodes a business rule (a discount, a limit, a status change), it probably belongs on a domain object.

Application services and domain services

The word 'service' means two different things in Domain-Driven Design (see the Domain-Driven Design topic). Eric Evans describes the application layer as thin: it coordinates tasks and delegates work to domain objects, and holds no business rules of its own.

  • Lives in — The service layer (application layer) — The domain layer
  • Holds — Use case steps: transaction, access checks, calling other systems — A business rule that does not fit one entity
  • Knows about — Repositories, the unit of work, gateways — Only domain objects
  • Example — OrderService.place_order — PricingPolicy.total_for(order, customer)
  • Test with — In-memory repositories and fake gateways — Plain objects, no fakes at all

The quick test: if the class would still make sense with no database and no network, and it decides something the business cares about, it is a domain service.

Transaction script or domain model

Fowler's catalog names three ways to organize domain logic: Transaction Script, Domain Model and Table Module (one class per database table). The first two are the ones interviewers contrast. A Transaction Script puts the logic of each request in one procedure. A Domain Model is a network of objects that hold both data and behavior. The service layer works with either one, but it looks very different on top of each.

  • Where the rules live — Inside the service method — On entities and domain services
  • Service method length — Long: it does everything — Short: it coordinates
  • Fits — Simple logic, few rules, CRUD screens — Many rules and many variations
  • Main risk — Rules duplicated across scripts as they grow — More classes to design up front

A small CRUD admin screen is fine as a transaction script. The trouble starts when a domain model exists in name only and the services do all the work; that is the anemic domain model, covered below.

One request, end to end

The controller stays thin. It turns HTTP into a call and the result back into HTTP; everything between is the service's job.

One transaction per use case

A use case is the natural transaction boundary. The service method begins one unit of work, every change made through the repositories joins it, and the method either commits all of it or rolls all of it back. Repositories never commit on their own (the Repository Pattern topic covers the Unit of Work in detail).

The example below places an order: it reserves stock, saves the order and charges the card. A fake gateway declines the second order, and the unit of work rolls the stock back. The confirmation is sent only after a successful commit.

The failure order costs 4 x 900 + 8 x 250 = 5,600 cents, over the 5,000 limit, so the gateway declines it after the stock was already reserved. The rollback is what makes that safe.

> [!IMPORTANT] > A card charge is not part of the database transaction. If the charge succeeds and the commit then fails, rolling back the database does not refund the card. Real systems charge last, make the charge idempotent with a key, and refund or retry when the commit fails.

Where the pattern goes wrong

  • Anemic domain model — Entities are only fields with getters and setters; services hold every rule — Move each rule onto the object whose data it uses
  • God service — One OrderService with forty methods covering checkout, refunds, reports and admin — Split by use case group: CheckoutService, RefundService
  • Services calling services in loops — for id in ids: customer_service.get(id), each call opening its own transaction — One batch method, one transaction, one query
  • Fat controller — The controller loads rows, checks rules and commits — The controller calls one service method and maps the result
  • Side effects inside the transaction — The email is sent, then the commit fails — Send after commit, or write an outbox row in the same transaction

Fowler's bliki entry on the anemic domain model calls it an anti-pattern: the objects look like a domain model but carry little behavior, so you pay for the mapping layer and get procedural code anyway. He keeps the service layer, but thin, on top of a domain model with real behavior.

What you will be able to do

  • Define the Service Layer with its source (Randy Stafford, in Fowler's PoEAA) and say what problem it solves.
  • List the five steps of a service method: authorize, load, act, commit, side effects.
  • Tell an application service from a domain service with one example of each.
  • Choose between a transaction script and a domain model for a given amount of business logic.
  • Put the transaction boundary around one use case and explain why the payment call needs extra care.
  • Name and fix the anemic domain model, the god service and services calling services in loops.

Interview questions

  • What belongs in a service layer, and what belongs in the controller?
  • What is the difference between an application service and a domain service?
  • Where does the transaction start and end, and what about calls to other systems?
  • What is an anemic domain model, and how does a service layer cause it?
  • Your services call each other in loops and one service has grown to forty methods. How do you fix it?

Sign up free to read the full topic

More Low-Level Design topics

Start free on Cariara · Coding problems · System design studio