Service in DDD: When Business Logic Doesn’t Belong to a Single Entity

Central domain service coordinating business rules between multiple entities

Once entities and value objects are in place, the next practical question follows quickly: where should a piece of business logic live when it doesn’t clearly belong to any single object? This is the question that the domain service answers in Domain-Driven Design (DDD). A domain service is not just another class, and it has nothing to do with technical microservices. It is a first-class home for behavior — the decisions and coordination that span multiple domain concepts.

This article continues from the previous post on objects (entities and value objects) and sets up the next one on modules. Across the DDD series, objects model what things are, services model what happens between them, and modules organize how those concepts cluster together. A domain service sits at the seam between objects and the higher-level structure of the model, which is why getting it right matters so much for product complexity.

Three questions shape the rest of the discussion: why domain services exist, what makes a good one (stateless, intent-revealing, correctly layered), and how a domain service differs from an application service and an infrastructure service. A concrete credit-transfer example ties the principles together.


Why Domain Services Exist: Decisions That Span Multiple Concepts

Shared business decision connecting multiple domain concepts

Some business behavior simply does not belong to a single object. Entities and value objects are excellent at modeling specific things — a Project, an Application, an Interview Slot, an Account. But a product is full of behaviors that are not about ownership of a thing. They are about decisions and coordination across several things. That gap is exactly where domain services exist.

A service is usually the right home when one or more of these conditions hold:

  • The operation spans multiple domain concepts.
  • Placing the logic inside a single entity feels forced or unnatural.
  • The action itself is central to the business narrative.
  • The operation is pure logic, not state owned by any one object.

If the logic only manipulates the internal state of one entity, start there — keep it on the entity. A service is the right tool only when no single entity is a comfortable home. Typical examples include approving a loan after several validations, assigning an interviewer based on availability, transferring credits between accounts, and deciding eligibility against multiple business rules. These actions share a pattern: more than one domain concept is involved, the operation represents a business decision, and no single entity is the natural owner.

Force this kind of behavior onto an entity and the model starts to bend. The entity reaches into other entities, responsibilities blur, and the clarity of each domain concept erodes. A domain service gives this logic a home without distorting the entities it touches.

A useful analogy: a domain service plays the role of a notary in a real-estate transaction. The seller (entity A) manages their property, and the buyer (entity B) manages their funds. But the act of transferring ownership doesn’t belong to either side. The notary — the domain service — checks both parties’ conditions and makes the transaction valid under the rules. The notary is not a third asset; it is a piece of authoritative behavior that exists precisely because the action is shared.


What Makes a Good Domain Service: Stateless, Intent-Revealing, Correctly Layered

Three balanced properties representing a well-designed domain service

Not every function is a domain service. A good domain service shares three properties, and each of them does specific work in protecting the model.

PropertyWhat it means in practiceWhy it matters
1. StatelessThe service holds no long-lived data. It operates only on the entities, value objects, and inputs passed to it at the moment of execution.Keeps responsibility clear and prevents the service from quietly turning into a hidden state holder.
2. Intent-revealingThe name reflects a business action, not a technical step. Good: scheduleInterview, approveApplication. Weak: processData, handleLogic.Lets every teammate read the domain behavior off the code. The name carries the meaning.
3. Correctly layeredThe service’s responsibilities match the layer it lives in. A domain service enforces business rules; orchestration and integration belong elsewhere.Prevents business rules from leaking into orchestration code or infrastructure code, and the reverse.

A good domain service is defined by its role in the model, not by its structural shape.

First, a domain service is deliberately stateless. It does not hold long-lived data and does not represent a business object in its own right. Instead, it operates on existing entities and value objects that are handed to it. Statelessness is what keeps the service from silently accumulating responsibilities that should sit on an entity or a repository.

Second, a domain service expresses domain intent. The name and purpose should reflect a business action that means something to a product stakeholder, not an internal technical step. A name like approveApplication invites a product manager into the conversation; a name like processData pushes them away.

Third, a domain service lives in the right layer. Services exist at several layers — application, domain, infrastructure — and each layer has a different job. The domain service is specifically responsible for enforcing business rules. Orchestration and integration logic belong outside the domain layer. Keeping that boundary clean preserves the model and reduces the slow erosion that happens when business rules get smeared across layers.

When these three properties work together, the service stops being a dumping ground for misplaced logic and starts reinforcing the domain model itself.


Application Service vs Domain Service vs Infrastructure Service

Three service layers separating orchestration business rules and integrations

Each layer has a job — and, just as importantly, things it deliberately avoids. The same user action passes through application, domain, and infrastructure services, and each one contributes something different.

LayerWhat this service is responsible forWhat it deliberately avoids
Application ServiceCoordinates a use case end to end: receives the request, calls domain logic, manages the transaction, returns the result.Making business decisions or embedding domain rules.
Domain ServiceEnforces business rules that span multiple domain concepts — availability, eligibility, conflict resolution.Flow control, transactions, and external side effects.
Infrastructure ServiceHandles interaction with external systems: calendars, notifications, third-party APIs.Knowing or enforcing business rules.

Read horizontally, the three layers describe a clean division of labor: orchestration, rules, and integration. An application service knows the shape of a use case but not its rules. A domain service knows the rules but not how to start a transaction or send an email. An infrastructure service knows how to talk to the outside world but knows nothing about why.

This separation is what makes the layered architecture from earlier in the DDD series work in practice. Without it, a single “service” class ends up holding a transaction, a business rule, and an API call all at once — and every change to one of those concerns risks breaking the other two. With it, each layer can evolve at its own pace, and each kind of change has an obvious home.


Example: A Credit Transfer Domain Service

Domain service coordinating a credit transfer between two projects

Consider a platform where every team receives a monthly allowance of usage credits and can transfer credits between its projects. A simple, recognizable scenario — and a clean illustration of where a domain service belongs.

┌────────────────────┐        ┌────────────────────┐
│ Project A (entity) │        │ Project B (entity) │
│--------------------│        │--------------------│
│ credits: 100       │        │ credits: 40        │
└─────────┬──────────┘        └─────────┬──────────┘
          │       move 30 credits       │
          └──────────────▶──────────────┘
┌────────────────────┐        ┌────────────────────┐
│ credits: 70        │        │ credits: 70        │
└────────────────────┘        └────────────────────┘

Transferring credits is not a property of a single project. It is a business action, and it comes with a small set of rules that must hold every time:

  • Credits cannot go negative.
  • The transfer must either succeed completely or fail completely.
  • Both projects must exist at the time of the transfer.

No single project can enforce these rules on its own. The operation only makes sense when both sides are considered together. That is precisely the signal that the logic does not belong inside an entity — it belongs in a domain service.

A clean way to see how the responsibilities split across layers is to follow a single user action down through the architecture:

Credit Transfer (one user action)
─────────────────────────────────

┌───────────────────────┐
│ Presentation Layer    │ UI / API
│ "Transfer 30 credits" │
└──────────┬────────────┘
           │ request
           ▼
┌───────────────────────┐ Use case
│ Application Layer     │ coordination
│ TransferCreditsUseCase│
│ - start transaction   │
│ - call domain service │
└──────────┬────────────┘
           │ domain operation
           ▼
┌────────────────────────┐ Business rule
│ Domain Layer           │ enforcement
│ CreditTransferService  │
│ - verify amount > 0    │
│ - prevent negative bal.│
│ - apply changes to A,B │
└──────────┬─────────────┘
           │ persistence
           │ + side effects
           ▼
┌───────────────────────┐ Technical
│ Infrastructure Layer  │ implementation
│ ProjectRepository     │
│ - load A, B           │
│ - save updated bal.   │
│ EventPublisher/       │
│ Notifier              │
└───────────────────────┘

Each layer has a single, readable job. The presentation layer captures intent. The application layer (TransferCreditsUseCase) opens the transaction and calls the domain. The domain layer (CreditTransferService) enforces the actual business rules — the credits-cannot-go-negative rule, the all-or-nothing rule, the both-projects-must-exist rule. The infrastructure layer loads and persists the projects and publishes whatever events the rest of the system needs.

This is what makes credit transfer a natural domain service. The operation involves multiple entities, the rules apply across those entities, and the action does not belong to either project alone. And notice how the example satisfies the three properties from the previous section: CreditTransferService is stateless (it holds no long-lived data, only the projects passed in), its name expresses domain intent rather than a technical step, and it lives in the correct layer (domain, not application or infrastructure). Three principles, one example, one clean home for the rule.


Conclusion

A domain service is best thought of as a first-class citizen for behavior — not another entity, and not a technical microservice. It exists because some business decisions span multiple domain concepts, and forcing them onto a single entity bends the model. When the action is shared, the service is the right home.

Three properties keep a domain service honest:

  • Stateless — operates only on what is passed in, holds no long-lived data.
  • Intent-revealing — its name reads as a business action, not a technical step.
  • Correctly layered — enforces business rules, and leaves orchestration and integration to other layers.

Use those three as a checklist whenever a piece of logic doesn’t fit neatly inside an entity or a value object. If all three hold, a domain service is almost certainly the right answer.

In the next part of the series, we move from services to modules — the way related concepts (including domain services like the one above) get grouped into meaningful units. Modules are where domain design starts to influence not only the code but also how product teams organize themselves and how systems scale.


Domain-Driven Design Series

(1) Domain-Driven Design as a Product Management Framework

(2) What is a Domain Model? Definition, Quality Criteria, and Examples

(3) DDD Layered Architecture: The Role of Each Layer in Domain-Driven Design

(4) Object in DDD: Building Blocks of the Domain Model

(5) Service in DDD: When Business Logic Doesn’t Belong to a Single Entity

(6) Modules and Bounded Contexts in DDD: Structuring Domains for Scale

(7) Aggregate Root Pattern in DDD: Consistency Boundaries Explained

(8)Factory Pattern in DDD: Controlling How Aggregates Come Into Existence

(9) Repository in DDD: Pattern, Principles, and the Domain Model Checklist