| name | application-layer |
| description | Use this skill when writing controllers, structuring use cases, or implementing Service Layer / Application Layer and Command/Query/Handler (CQRS) patterns in PHP applications. Triggers on questions about thin controllers, handler responsibilities, service definition, or DTO usage. |
Application Layer & CQRS Patterns for PHP
Service Layer = Application Layer
In DDD terminology, the Service Layer is interchangeable with the Application Layer. A service (also called a handler, operation, action, or interactor) represents one business use case: it orchestrates and coordinates work, controls the transaction, and manages side effects. It does not make business decisions — it is a workflow, not a business rule. The actual decisions live in the domain (rich objects, policies, or a table module). See service-layer.md for the full definition, benefits, the overuse anti-pattern, and how it sits above the domain-modeling spectrum. A Command/Handler here is the in-process expression of a use case; the distribution-patterns skill covers exposing that same use case remotely as a coarse-grained Remote Facade.
When to Use a Service Layer (and the Anti-Pattern to Avoid)
Use a Service Layer whenever more than one delivery channel (HTTP, CLI, queue) needs the same orchestration, or when you want a clear, testable entry point per use case. But do not overuse it: the common failure is Controller → Service → Repository → Entity where the entity is anemic and the service holds all the logic. That is a Transaction Script wearing extra indirection — the fault is an anemic domain, not the pattern. Push business rules down into the domain; keep the service as pure orchestration.
When to Use CQRS (and When NOT to)
CQRS separates read and write models, but adds complexity. Use CQRS only when a bounded context has genuinely divergent read/write workloads. Skip CQRS for simple CRUD, interfaces without complex reads, or early-stage projects where premature separation slows delivery.
| Use CQRS For | Use Simple Controller/Repository For |
|---|
| Bounded contexts with divergent read/write workloads | Simple CRUD, single operation per endpoint |
| A read model with 10+ projections or dedicated reporting | Single or few read paths |
| Scaling reads and writes independently | Uniform access patterns |
Most systems do not need CQRS. Start with simple controllers and repositories. Introduce CQRS only when a specific read path has become a clear performance or organizational bottleneck.
System Overview
The application layer orchestrates use cases while keeping framework adapters thin and domain rules explicit. It serves as the bridge between delivery mechanisms (HTTP, CLI, queues) and the core domain/infrastructure logic. This skill provides structural rules for controllers, handlers, and CQRS patterns.
Principles That Shape This Layer
These cross-cutting principles explain why the rules below exist. Full detail in the domain-modeling references.
- Dependency Inversion (DI). High-level use-case code depends on ports (interfaces), never on concrete databases, mailers, or gateways. Concrete adapters are supplied at the composition root. This is what lets a handler run against in-memory doubles in a test and is the backbone of Hexagonal architecture.
- Single Responsibility (SRP) + Separation of Concerns. A handler owns one use case; it coordinates, it does not decide. Business rules belong in the domain, persistence in a repository, delivery in the controller. A class with 5+ unrelated collaborators usually violates this.
- Interface Segregation. Ports should be narrow and role-specific (e.g.,
PaymentGateway exposes only what the use case needs), so adapters stay thin and clients are not forced to depend on methods they ignore.
- Layer direction. Dependencies point inward: delivery → application → domain; infrastructure implements domain ports but is never depended on by domain. Reversing this direction (framework types leaking into the domain) is the core design debt smell — see below.
See solid-principles.md and clean-code-foundations.md.
Numbered Workflows
0. Defining a Service / Use Case
If you are creating or reviewing a use-case coordinator (service, handler, operation, action, interactor):
- Name it after the use case, not the CRUD verb. E.g.,
CompleteEnrollment, InvoiceCustomer, not CustomerService.update.
- Make it the single entry point. One operation per use case; the controller/CLI/queue adapter only adapts input and delegates here.
- Orchestrate, do not decide. The service loads state, calls domain objects/policies/table modules to make the business decision, persists the result, and publishes side effects. It must not contain the business rules itself.
- Bound the transaction at the service. The transaction begins when the operation starts and ends when it returns — that is the unit of work.
1. Refactoring Controllers
If a controller contains business logic, database queries, or large DTO building:
- Extract the Core Logic. Move the business rules into an application handler or domain object.
- Adapt the Edge. Refactor the controller to only read inputs (route params, request body, etc.), delegate to the handler, and convert the result into an HTTP response.
2. Implementing CQRS Patterns
If creating a new use case that needs to be accessed via multiple delivery channels (e.g., Web and CLI):
- Determine the Operation Type. Is it a write/state-transition (Command) or a read/projection (Query)?
- Create the Command/Query Object. Define an intent-revealing object if the use case has multiple inputs or needs validation at the application boundary.
- Create the Handler. Write a handler that executes the specific Command or Query.
- If the operation is extremely simple (e.g., a single repository fetch), skip the Command/Handler pair and let the controller call the repository directly.
3. Creating DTOs and Command Objects
If deciding whether to introduce a new DTO or Command object:
- Evaluate Intent. Does the object carry meaningful business intent across a boundary? If yes, create it.
- Evaluate Necessity. Does the object merely mirror the request body or exist just to satisfy a generic pattern rule? If yes, skip it and use scalar arguments or arrays.
Recognizing Problems in an Existing Codebase
When analyzing an established codebase, look for these application-layer smells (signals, not automatic defects — fix them only when you next touch the area, per the architecture-migration trigger rule):
- Fat controller. The controller contains business logic, DB queries, or large response/DTO building instead of delegating to a handler.
- Anemic domain behind a service. The service holds all the decisions and the entity is a data shell — a Transaction Script wearing extra indirection.
- Service chaining.
Service A → Service B → Service C, so no single handler owns a complete business result and failures/transactions become hard to reason about.
- Framework globals in handlers. The handler pulls
request, session, or the container singleton instead of receiving plain inputs, making it untestable without the framework.
- Business rules living in the service. Decisions, invariants, or calculations sit in the orchestrator rather than in domain objects or policies.
- Leaky abstractions / reversed layer direction. Framework or ORM types (Request, Eloquent models, DB rows) cross into the domain or application layer; infrastructure is depended on by the domain instead of implementing a port. This is the central design debt smell.
- Shotgun surgery. A single business change forces edits in 5+ files because one responsibility is scattered. Consolidate the behavior into one object/handler.
- Circular dependencies between modules. Two handlers or services import each other to get the job done; break the cycle by introducing a port or moving the shared logic down into the domain.
Boundaries
Always Do
- Always keep controllers thin; they should act purely as HTTP adapters.
- Always give each use case a single, well-named entry point in the Service/Application Layer (a service or handler).
- Always use the Directory Structure Templates when implementing CQRS.
- Always let the handler load data, call domain objects/policies, save state, and publish events.
- Always treat the handler as an Application Service: it orchestrates the domain and manages side effects, but does not contain business rules itself (see service-layer.md).
- Always push business decisions down into the domain; if the entity is anemic and the service holds everything, you have recreated a Transaction Script with extra indirection.
Ask First
- Ask before introducing intermediate Command classes or Request DTOs if they do not add meaningful domain context.
Never Do
- Never chain services together (e.g., Service A calls Service B calls Service C). A handler should own one complete business result.
- Never place business rules (decisions, invariants, calculations) inside a service; delegate them to the domain.
- Never place framework-specific globals (like request or session singletons) inside Application Handlers or Domain objects.
- Never let controllers contain persistence orchestration or cross-context policies.
Related Skills
- action-domain-responder: the framework "controller" is an ADR Action that delegates to this Application Layer (the Domain in ADR) and hands off to a Responder. That skill corrects the "Web MVC" misnomer.
- distribution-patterns: the same use case exposed remotely as a coarse-grained Remote Facade.
- coordination-patterns.md: the Template Method and Mediator patterns as they shape use-case flows and inter-object coordination (Mediator realized as domain events).
- solid-principles.md and clean-code-foundations.md: the SOLID and clean-code lenses that motivate the boundaries above.