Table of Contents
13. Services — When Behavior Doesn’t Belong to an Entity
By now, we've seen how our domain is shaped by concepts:
- Value Objects, express meaning.
- Entities, express identity.
- Collections, group things that belong together.
- Repositories, store and retrieve entities.
But once we start representing real behavior,
a natural question
appears:
Where do I put logic
that doesn't belong to any 'single' entity?
Some actions involve 'multiple objects'.
Some actions involve 'external systems'.
Some actions coordinate steps that 'no single object' “owns”.
→ This is where the idea of 'services' comes in.
1. What a Service Really Is
A 'service' is simply:
- A 'named place' for behavior that
doesn't belong to a 'uniquely single' entity.
A 'service' is not a special kind of class.
Not a framework construct.
Not a pattern we must memorize.
Just a home for behavior
that lives between objects.
Think of our domain like a small workshop:
- Entities are the tools.
- Value Objects are the measurements.
- Repositories are the drawers where tools are stored.
But sometimes
we need someone or something to perform
an action
like, for example:
- assign a ticket
- send a notification
- calculate a discount
- publish a post
- process a payment
These actions don't belong inside a single entity.
They belong to the interaction between things.
That interaction is a service.
2. Why Services Exist
'Entities' should represent → long‑lived concepts.
'Value Objects' should represent → meaning.
'Repositories' should represent → storage.
But
'behavior' often crosses boundaries.
For example:
- Assigning a ticket involves a Ticket, a User, and a rule.
- Publishing a blog post involves a Post, an Author, and notifications.
- Placing an order involves a User, Products, Inventory, and payment.
No single entity “owns” these actions.
So, 'a service'
gives the behavior a clear, intentional home.
3. A Simple Service Example
Here's a gentle example of a service that assigns a ticket to a user:
class TicketAssignmentService { public function assign(Ticket $ticket, User $user): void { $ticket->assignedTo = $user->id; $ticket->status = 'assigned'; } }
Notice what's happening:
- The service receives the objects it needs.
- It performs the action.
- It doesn't store anything.
- It doesn't own the entities.
- It simply coordinates behavior.
This is the essence of a service.
4. What Services Are Not
Services are often misunderstood.
They are not:
- “God objects”
- dumping grounds for random logic
- replacements for entities
- controllers
- repositories
- utility classes
A service should have:
- a clear name
- a clear purpose
- a clear action
If we can't name it cleanly,
the behavior probably belongs somewhere else.
see below…
5. When Behavior Belongs in an Entity Instead
Some actions do belong inside an entity.
For example:
$ticket->close(); $ticket->reopen(); $ticket->markAsUrgent();
These are actions the ticket performs on itself.
They belong to the entity because they change the entity's own state.
But:
$ticketAssignmentService->assign($ticket, $user);
This involves two entities and a rule.
It belongs in a service.
So…
6. When Behavior Belongs in a Service
Use a service when the behavior:
- involves multiple entities
- involves external systems (email, API, payment)
- coordinates steps that no single object owns
- expresses a business rule that lives between objects
- is procedural rather than state‑changing
- doesn't belong to any one entity's identity or meaning
Examples:
- PaymentProcessingService
- EmailNotificationService
- OrderPlacementService
- PasswordResetService
- TicketAssignmentService
These names read like small stories. That's the goal.
7. Services and Repositories
Repositories store and retrieve entities.
Services use repositories to perform actions.
For example:
$user = $userRepository->find($id); $ticket = $ticketRepository->find($ticketId); $ticketAssignmentService->assign($ticket, $user); $ticketRepository->save($ticket);
The flow is:
- repositories provide the entities
- services perform the behavior
- repositories save the results
This keeps our domain clean and expressive.
8. The Mental Model
A service is:
- a named place for behavior
- a coordinator of multiple entities
- a holder of business rules
- a bridge to external systems
- a home for actions that don't belong to any one object
A service is not a thing in our domain.
It is an action in our domain.
Use services when:
- the behavior doesn't belong to an entity
- the behavior involves multiple objects
- the behavior needs a clear, intentional home
'Services' help our code read like
the story of our application.
9. When to Ask AI for a Service
The moment when behavior lives between objects
Conversation Example
We ask:
“Assign a ticket to a user.”
AI writes:
$ticket->assignedTo = $user->id; $ticket->status = 'assigned';
Then we ask:
“Send a notification when that happens.”
AI adds email logic.
Then we ask:
“Log the assignment.”
AI adds logging.
Now we “feel it”:
- multiple objects involved
- external systems involved
- coordination happening
- not the ticket’s job
This is the moment to say:
“Give me a TicketAssignmentService.”
AI produces:
$ticketAssignmentService->assign($ticket, $user);
Rule of Thumb
If behavior touches multiple objects or external systems
— ask for a Service.
Summary
Services introduce the idea that not all behavior belongs inside objects. Some actions live between objects — in the space where coordination happens. If this feels abstract at first, that’s normal. Let the examples settle, revisit them when needed, and the shape of our domain will become clearer with each pass.
Tony de Araujo —New York
