Hello! Welcome back to our course on Domain-Driven Design.
In our previous lessons, we built a solid foundation by exploring the core building blocks of a domain model: Entities, Value Objects, and Aggregates. We also saw how the Repository pattern helps us abstract away persistence details, keeping our domain model clean.
Today, we'll move up a layer in our architecture to answer a crucial question: where does the business logic that coordinates these domain objects live? This brings us to the topic of Services. Our goal for this lesson is to clearly distinguish between two key types: Domain Services and Application Services. Understanding their distinct roles is fundamental to creating a well-structured and maintainable application that adheres to DDD and Clean Architecture principles.
By the end of this lesson, you will be able to:
- Explain the role of Application Services in orchestrating use cases.
- Explain the role of Domain Services in encapsulating core business logic.
- Distinguish between the two based on their location, dependencies, and primary focus.
Let's get started.
1. The Core Distinction: "How" vs. "What"
At the heart of the distinction between Application and Domain Services is the separation of how an operation is performed from what the operation entails in terms of business rules.
- Application Services are concerned with the "how." They orchestrate the steps required to fulfill a specific use case or user request. Think of them as the project managers of your application's features.
- Domain Services are concerned with the "what." They encapsulate significant, stateless business logic that doesn't naturally belong to a single Entity or Value Object. They represent core business knowledge.
To get a quick overview, let's watch a short segment from a video that introduces these concepts within the context of a DDD project structure.
Domain-Driven Design: The Last Explanation You'll Ever Need
This video, 'Domain-Driven Design: The Last Explanation You'll Ever Need' from Software Developer Diaries, provides a brief introduction to services. Focus on how it separates services into different architectural layers.
Please watch the 'Domain and Application Services' section from 00:13:26 to 00:15:30. Pay attention to the distinction made between services in the 'application' folder versus the 'domain' folder.
As the video illustrates, these services live in different layers of the architecture, which reflects their different responsibilities. Application Services are closer to the "outside world" (UI, APIs), while Domain Services are deep inside the core domain model.
2. Application Services: The Orchestrators
An Application Service's primary job is to handle a single use case. It coordinates the various components needed to complete a task, such as:
- Receiving input data (often as Data Transfer Objects, or DTOs).
- Retrieving one or more aggregates from a repository.
- Invoking methods on those aggregates or calling a domain service to perform business logic.
- Using a repository to save any changed aggregates.
- Notifying other parts of the system (e.g., by publishing domain events or sending emails).
Crucially, an Application Service does not contain business logic itself. It delegates that responsibility to the domain model (Aggregates and Domain Services).
To see this in action, the following video provides an excellent example of the Application Layer's role.
Clean Architecture vs Domain-Driven Design (DDD) - Understand the Difference
The video 'Clean Architecture vs Domain-Driven Design (DDD)' by Amichai Mantinband clearly explains the role of the application layer. This will help solidify your understanding of an Application Service's orchestration duties.
Watch the segment 'Application Layer: Orchestrating Use Cases' from 00:04:35 to 00:06:09. Notice how it describes the process of opening a bank account as a sequence of coordinated steps.
In the video's example, the create bank account use case is a perfect candidate for an Application Service. It orchestrates calls to a government service and the database but doesn't decide whether the user is eligibleβthat's a core business rule.
3. Domain Services: The Keepers of Core Logic
So, where does that eligibility logic go? If a business rule is complex and involves multiple domain objects (e.g., checking a customer's credit score against their order history to approve a loan), it doesn't neatly fit into a single Aggregate. Placing it in an Application Service would pollute the orchestrator with core business logic.
This is where Domain Services come in. They are created for domain operations that feel like verbs without a natural home on an entityβconcepts that, as Eric Evans puts it, "just aren't a thing."
A proper Domain Service has three key characteristics:
- The operation relates to a domain concept but doesn't naturally belong to a single Entity or Value Object.
- Its interface is defined purely in terms of other domain model elements (Entities, Value Objects).
- It is stateless. It performs a calculation or operation and returns a result, but it doesn't hold any state of its own.
Let's return to the previous video to see how the Domain Layer complements the Application Layer.
Clean Architecture vs Domain-Driven Design (DDD) - Understand the Difference
This next segment from the same video clarifies the Domain Layer's role, which is where Domain Services reside.
Now, watch the 'Domain Layer: Business Logic and Entities' section from 00:05:58 to 00:07:10. This explains where the actual business decisions are made.
The video highlights that the decision of whether a user is eligible to open an account belongs in the domain layer. A CustomerEligibilityService would be a perfect example of a Domain Service. It might take a Customer object and a CreditReport object and return a boolean result, without ever touching a database or sending an email.
4. A Deeper Dive: Location, Dependencies, and Examples
To solidify these concepts, let's examine a detailed article that provides clear definitions and code examples.
Application Services VS Domain Services in DDD
The article 'Application Services VS Domain Services in DDD' by JC Navarrete offers a comprehensive comparison. We'll focus on the sections that define each service's purpose, location, and dependencies.
Please read the following sections from the article: 'Purpose and Responsibility': This section clearly defines the roles of each service. 'Location and Dependencies': Pay close attention to what each service is allowed to depend on. 'Focus': This summarizes the 'how' vs. 'what' distinction. 'TypeScript Examples': Review the code for both the AccountOpeningService (a Domain Service) and the OpenAccountUseCase (an Application Service). Even though it's TypeScript, the structure clearly shows the difference in their responsibilities and dependencies.
After reading, consider these points from the examples:
- The Domain Service (
AccountOpeningService) is pure. It has a single method,validateAccountOpening, that contains a business rule (validateMinimumBalance). It has no dependencies on repositories or event publishers. It lives entirely within the domain. - The Application Service (
OpenAccountUseCase) is an orchestrator. It depends on the domain service, a repository, and an event publisher. Itsexecutemethod defines the workflow: create an account object, delegate validation to the domain service, save the result via the repository, and publish an event.
This clear separation makes the system much easier to test and maintain. The core business logic in the Domain Service can be unit-tested in isolation, while the Application Service can be tested to ensure it correctly coordinates the different components.
For a final, concise summary, let's look at another resource that provides a handy "cheat sheet."
Unpacking the Layers of Clean Architecture β Domain ...
The article 'Unpacking the Layers of Clean Architecture' by Dan Mackay includes some excellent summary tables that are perfect for a quick review.
Please review the two tables under the 'Service Examples' and 'Service Cheat Sheet' headings. These provide concrete examples and a high-level summary of each service's purpose.
The example of a TaxCalculationService (Domain) vs. an AccountExportService (Application) is another great illustration of the difference in their responsibilities.
Conclusion
Today we've unpacked the distinct roles of Application and Domain Services, a critical pattern for maintaining separation of concerns in a DDD architecture.
Here are the key takeaways:
- Application Services orchestrate use cases (the "how"). They live in the Application Layer, coordinate domain and infrastructure components, and are the direct clients of the domain model for executing user-facing tasks.
- Domain Services encapsulate core business logic that doesn't fit in a single Entity or Value Object (the "what"). They are stateless, live in the Domain Layer, and should be completely ignorant of infrastructure concerns like databases or APIs.
- This separation ensures your core domain logic remains pure, portable, and highly testable, while application-specific workflows are managed cleanly in a separate layer.
In our next lesson, we will put this theory into practice. We will implement a stateless Domain Service in Python to handle a business rule that spans multiple aggregates, and then we'll create an Application Service to orchestrate its use.