In the previous article about where a business rule should live I separated the controller, application service, and domain model.
Even after that, the word "service" remained too broad for me for quite a while.
A class does something? Call it a Service. It works with several objects? Service. It contains some logic? Again, Service.
The problem is that Application Service and Domain Service may both be called services while answering completely different questions.
Start with One Simple Use Case
Use case: Activate product Steps:1. load current state2. check domain rule3. change state4. save result5. trigger secondary workThis kind of use case is exactly where I used to mix two responsibilities.
One method may need to load data, open a transaction, check a rule, change state, persist the result, and possibly start secondary work.
At first glance, all of it looks like one "business operation." But not all of that code is domain logic.
For Me, an Application Service Coordinates the Use Case
final class ActivateProduct{ public function handle( ProductId $productId, ): void { DB::transaction(function () use ( $productId, ) { $product = $this->products ->getForUpdate($productId); $product->activate(); $this->products->save( $product, );In this example, the application service does several important things.
- opens the transaction;
- loads the required aggregate from the repository;
- invokes the domain action;
- persists the result.
It knows the order of the steps, but it should not know every detail of why activate() is valid or invalid right now.
Transactions, Repositories, and External APIs Do Not Make a Service Domain Logic
Application Service may coordinate: - transaction- repository- cache- queue- email- external API- file storage These are use-case concerns,not domain meaning.This was an important boundary for me.
If a class works with a repository, queue, or external API, that does not make it a Domain Service.
In fact, this often points directly to application responsibility: the service coordinates how the use case moves through the system.
The domain should not care which queue carries a message or which client calls an external API.
A Domain Service Appears When a Rule Has No Natural Single Owner
final class ActivationEligibility{ public function canActivate( Product $product, Policy $policy, ): bool { return $product->isReady() && $policy->allowsActivation(); }}This is a different situation.
The service does not open a transaction or send a message. It answers a domain question.
The rule uses Product and Policy, but neither object naturally owns all knowledge of the other.
In that case, a dedicated Domain Service is more honest than forcing the rule into one entity.
I Do Not Create a Domain Service When the Rule Naturally Belongs to an Entity
$product->activate();This was another mistake I made: whenever a rule became slightly more complex, I wanted to create a Domain Service immediately.
But if the rule concerns only Product state and Product has enough information to make the decision, a method on Product reads more naturally.
A Domain Service is not a "more advanced architecture layer." It is an option for a genuine domain rule without one natural owner.
Sometimes the Rule Belongs to a Value Object
final readonly class Version{ public function canUpgradeTo( self $target, ): bool { return $target->isGreaterThan( $this, ); }}If a rule naturally describes the value itself, a value object is often the better home.
This helps me avoid turning Domain/Services into another folder where everything goes when it does not fit into an entity.
What an Application Service Looks Like When It Starts Doing Too Much
final class ActivateProduct{ public function handle( Product $product, Policy $policy, ): void { if ($product->status !== 'draft') { throw new LogicException(); } if (! $product->hasRequiredData()) { throw new LogicException(); } if (! $policy->allowsActivation()) { throw new LogicException();This service may work, but its role has become unclear.
It knows Product state, readiness rules, Policy, and persistence details at the same time.
As soon as another use case needs some of these rules, we either duplicate them or reuse a large service where its coordination is not actually needed.
How I Separate the Same Use Case
final class ActivateProduct{ public function handle( ProductId $productId, ): void { DB::transaction(function () use ( $productId, ) { $product = $this->products ->getForUpdate($productId); $policy = $this->policies ->current(); if (! $this->eligibility->canActivate( $product,The roles are now visible directly in the code.
ActivateProduct knows the sequence of the use case. ActivationEligibility knows the rule that spans several domain concepts. Product knows how to change its own state after a positive decision.
This is not the only valid design, but this separation is much easier for me to maintain.
Another Clue: Look at What the Service Depends On
Application Service often depends on: - repository interfaces- transaction boundary- queue- mailer- external gateway- clock Domain Service should depend on: - domain objects- value objects- domain policies- domain abstractionsThis is not an absolute rule, but it is a useful check.
If a "Domain Service" suddenly needs Request, Response, an Eloquent builder, or a concrete HTTP client, I question whether it is really a domain service.
The closer a class is to the domain, the less it should know about transport mechanisms and concrete infrastructure.
Tests for the Two Services Look Different Too
public function test_policy_blocks_activation(): void{ $product = Product::ready(); $policy = Policy::activationDisabled(); self::assertFalse( $this->eligibility->canActivate( $product, $policy, ) );}A Domain Service test can often be written without a database, HTTP, or queue.
It tests the rule through domain objects.
An Application Service test is more interested in coordination: was the correct state loaded, was the domain action invoked, was the result saved, and did secondary work happen at the right time?
The Aggregate Helps Avoid Unnecessary Domain Services
Once the aggregate is clear, the choice becomes easier.
If a rule entirely protects the state of one aggregate, I first look for a place inside that aggregate. I discussed this in more detail in the article about aggregates and consistency boundaries.
A Domain Service becomes useful when the rule genuinely spans several domain concepts and has no single natural owner.
Service Responsibility Mistakes I Notice Faster Now
A few signals stand out to me:
- The Application Service contains long lists of state rules.
- The Domain Service sends email or works with HTTP.
- Every rule automatically gets a new Service class.
- Entities become only getters and setters while every decision lives outside.
- One Service makes domain decisions, opens transactions, and calls an external API at the same time.
- The class name ends in Service, but the code does not reveal whether it coordinates or decides.
In AVN Center, Repeated Use Cases Made the Difference Clear
While working on AVN Center, I did not begin with a perfect service split.
The distinction became clearer when the same invariant started appearing in several use cases while the surrounding application steps remained different.
This continues the idea from the first article in this DDD sequence: architecture becomes clearer as the system accumulates real rules.
I do not treat that as a flaw in the first version. Often we simply did not yet know enough to see the correct responsibility.
My Short Check: Application Service or Domain Service?
Does the class coordinate: - transaction- repository- external service- queue- use-case order -> Application Service Does the class express: - a business rule- a domain decision- logic spanning domain conceptsNow I Separate Coordination from the Meaning of the Rule
For me, the difference between Application Service and Domain Service became simple only through practice.
An Application Service answers: how do we execute this use case in the system?
A Domain Service answers: what is the correct domain decision when that decision does not belong to one object?
And if the rule naturally belongs to an entity or value object, a separate Domain Service is unnecessary.
I no longer use the word Service as a home for "all logic." I identify the responsibility first and only then choose the class.
An Application Service organizes execution. A Domain Service expresses a rule. If the rule has a natural owner, it may not need a service at all.



