agentleFS
AnyOrgNameAnyOrgName/php-skills/skills/architecture/service-layer/SKILL.md

service-layer

Skill4 starsChanged 4 months agoMITView on GitHub

Give each use case a thin application service that orchestrates domain objects and repositories, owns the transaction boundary, and exposes a clear entry point — keeping controllers thin and business rules out of the delivery layer. Use when the user wants one class per use case that coordinates domain objects and repositories to carry out an operation; the user wants to move business logic out of controllers so the delivery layer only translates requests and responses; the user wants a single place that begins and commits the transaction wrapping a whole use case. Keywords: service-layer, application-service, use-case, transaction, orchestration, boundary.

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 3 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈3

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Do not reach for a service layer as the place to put domain rules that belong on the entity or value object itself.

    line 66
  2. Distinctive:

    Do not add an application service that only forwards a call straight to a repository with no coordination or transaction.

    line 67
  3. Very specific:

    Create one application service for the use case with a plain input, e.g.

    line 78
  4. Distinctive:

    Not applicable — no I/O, auth, or untrusted input.

    line 125

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md
<!-- generated: do not edit — source: knowledge/atoms/architecture/service-layer/ --><!-- schema: atomVersion=3 skillVersion=2 graphVersion=1 --><!-- compile: @php-skills/compiler v1.0.0 (deterministic build) -->

Service Layer (Application Services)

When to use

  • The user wants one class per use case that coordinates domain objects and repositories to carry out an operation.
  • The user wants to move business logic out of controllers so the delivery layer only translates requests and responses.
  • The user wants a single place that begins and commits the transaction wrapping a whole use case.

When NOT to use

  • Do not reach for a service layer as the place to put domain rules that belong on the entity or value object itself.
  • Do not add an application service that only forwards a call straight to a repository with no coordination or transaction.

Core guidance

  • Give each use case its own service method — Model each use case as one application service (or one method on a focused service) with a clear name like PlaceOrder or registerUser.
  • Own the transaction at the service boundary — Begin and commit the transaction inside the application service so the whole use case is one atomic unit of work, rolling back on failure.

Quick recipe

  1. Create one application service for the use case with a plain input, e.g. final class PlaceOrder whose execute(PlaceOrderInput $input): OrderPlaced names the operation. (verify: The service takes a plain input object and no framework request)
  2. In execute(), open a transaction, load aggregates from injected repositories, call their behavior, persist them, then commit — rolling back on any failure. (verify: The whole use case commits atomically, and a thrown exception rolls it back)
  3. Have the controller map the request to the input DTO, call the service, and turn its result into a response — with no business logic of its own. (verify: The controller only translates request to input and result to response)

Best practices

  • Have the service accept a plain input object or scalars and return a plain result, not the framework request or an ORM entity, so it stays independent of the delivery mechanism
  • Inject repositories and external gateways into the service as interfaces, so the use case is wired to abstractions and can be tested with fakes
  • Let the service coordinate — load aggregates, call their behavior, save them — and push the actual rules down into the domain objects it uses

Anti-patterns

Anti-patternWhy it failsDo instead
One god service for a whole moduleA god service becomes a change magnet with tangled dependencies, and its methods share no cohesion beyond belonging to the same noun.Split it into one focused application service per use case, each depending only on the<br>collaborators that use case needs.
Business logic living in the controllerLogic in the controller cannot be reused outside HTTP and mixes delivery concerns with the use case, so the service layer's purpose is lost.Extract the orchestration and transaction into an application service the controller invokes<br>with a plain input object.
Returning the ORM entity from the serviceHanding back a mutable ORM entity lets the delivery layer change persistent state and couples views to the database schema.Return a plain result DTO with just the data the caller needs, mapping from the entity inside<br>the service.

See references/anti-patterns.md.

Common mistakes

  1. The service swelling into a transaction script — Letting all the business rules pile up inside the service method produces a long procedural transaction script with an anemic domain underneath. Fix: Move each rule onto the entity or value object it belongs to, leaving the service to load, invoke behavior, and save.
  2. Framework request objects leaking into the service — Passing the raw HTTP request or session into the application service ties the use case to the web delivery mechanism. Fix: Have the controller extract the needed values into a plain input DTO and pass that to the service, keeping the service delivery-agnostic.
  3. Nested services with tangled transactions — Having one application service call another, each opening its own transaction, produces nested or overlapping transaction scopes that are hard to reason about. Fix: Keep one transaction boundary per top-level use case; extract shared steps into domain services or plain collaborators that do not open their own transaction.

Version notes

  • Immutable input DTOs with PHP 8.1 readonly (PHP >=8.1) — PHP 8.1 readonly promoted properties make the service's input and result DTOs immutable in a couple of lines, so the use case receives a fixed request.
  • Property hooks tidy result DTOs in PHP 8.4 (PHP >=8.4) — From PHP 8.4, property hooks let a result DTO expose a computed field (a formatted total) without a separate getter, keeping the service's output object small.

Security

  • Not applicable — no I/O, auth, or untrusted input.

Testing

  • Activation and decision behavior are verified in eval/scenarios/architecture/service-layer.yaml.
  • Inject repositories and external gateways into the service as interfaces, so the use case is wired to abstractions and can be tested with fakes.

Additional resources

  • references/anti-patterns.md
  • references/core.md
  • references/examples.md
  • references/pitfalls.md
  • references/recipes.md
  • references/version-differences.md

Knowledge graph

Prerequisites: architecture/dependency-inversion
Related: architecture/cqrs, architecture/repository-pattern

<!-- graph-fragment: graph/fragments/architecture/service-layer.yaml -->

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.