0% прочитано

Application Service vs Domain Service: How I Separate Their Responsibilities

For a long time it was easy for me to call any non-trivial logic a service. In practice, one distinction helped: an application service coordinates the use case, transaction, repository, and external dependencies, while a domain service expresses a domain rule that does not naturally belong to one object.

14 жовтня 2026 р. 10 хв читанняLaravel

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.

The simplest rule that helped me: an Application Service coordinates a use case. A Domain Service makes or helps make a domain decision.

Start with One Simple Use Case

One use case contains both coordination and a domain decision
Use case: Activate product Steps:1. load current state2. check domain rule3. change state4. save result5. trigger secondary work

This 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

The Application Service coordinates the steps of 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

Coordinating external dependencies remains an application responsibility
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

The rule spans several domain concepts and does not fully belong to one of them
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

If Product owns the decision, a separate service is unnecessary
$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

Not every rule around data requires a separate service
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

The service is no longer coordinating, it is explaining the whole domain
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

The Application Service coordinates while the Domain Service answers the domain question
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

Dependencies often reveal the real responsibility
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 abstractions

This 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

The Domain Service is tested as a domain rule
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?

Three questions are often enough to reveal the responsibility
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 concepts

Now 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.