DEV Community

Bohdan Turyk
Bohdan Turyk

Posted on • Edited on

Domain vs Application Services: what is the difference

I've been learning and applying Domain-Driven Design (DDD) principles for a few years, and I often struggle with distinguishing between domain and application services. In this article, I’d like to share some observations and thoughts based on my experience. Hopefully, you’ll find something useful here!

Approach

I’ll first explore services in a complex domain and then in a simple one. After that, I’ll summarize my thoughts.

A Complex Domain

It seems easier to separate domain and application services in a complex domain. Let’s illustrate this with an example.

Imagine a banking application where we need logic to transfer money from one account to another. Below is a simplified snippet demonstrating possible application and domain services for that purpose:

// src/Domain/Entity/Account.ts

class Account {
    balance: number;
}

// src/Domain/Service/MoneyTransferDomainService.ts

class MoneyTransferDomainService {
    async transferMoney(fromAccount: Account, toAccount: Account, amount: number): Promise<void> {
        if (fromAccount.balance < amount) {
            throw new Error('Insufficient funds.');
        }

        fromAccount.balance -= amount;
        toAccount.balance += amount;
    }
}

// src/Application/MoneyTransferApplicationService.ts

class MoneyTransferApplicationService {
    async transferMoney(fromAccountId: number, toAccountId: number, amount: number) {
        const fromAccount = await this.accountRepository.findOneById(fromAccountId);
        const toAccount = await this.accountRepository.findOneById(toAccountId);

        if (!fromAccount) {
            throw new Error(`Account with id "${fromAccountId}" not found`);
        }

        if (!toAccount) {
            throw new Error(`Account with id "${toAccountId}" not found`);
        }

        await this.moneyTransferDomainService.transferMoney(fromAccount, toAccount, amount);
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the MoneyTransferDomainService encapsulates a crucial business rule: moving money between accounts. It belongs in the domain layer because it represents core business logic. Meanwhile, the MoneyTransferApplicationService acts as a coordinator, retrieving the necessary entities and delegating the core transfer logic to the domain service.

This separation makes sense. We have a main part that belongs to domain layer and a secondary that belongs to application layer. Let's explore at a few alternatives applicable to this example.

Possible Alternatives

1. Placing the logic inside the Account entity
  • This could lead to bloated entities (a “god class”).
  • It sometimes unclear which entity should contain the logic when multiple entities are involved. In a more complex example with PersonalAccount and BusinessAccount, it would be harder to decide where to place this logic.
2. Moving the logic to MoneyTransferApplicationService
  • This would cause domain logic to leak into the application layer.
  • The application layer would gain more responsibilities, some of which may seem redundant.

Hints for Identifying a Domain Service

  1. The logic is crucial for the business.
  2. The logic involves interactions between multiple entities.

A Simple Domain

Now, let’s consider an example from a simpler domain. Suppose we have a user management component where we need to update a User entity. Here’s a relevant snippet:

// src/Domain/Entity/User.ts

class User {
    name: string;
    email: string;
}

// src/Application/UpdateUserApplicationService.ts

class UpdateUserApplicationService {
    async update(id: number, name: string, email: string) {
        const user = await this.userRepository.findOneById(id);

        if (!user) {
            throw new Error(`User with id "${id}" not found`);
        }

        user.name = name;
        user.email = email;
    }
}
Enter fullscreen mode Exit fullscreen mode

Here, we only have an application service, which looks okay since updating a user’s details does’t look like a complex business rules that should be encapsulated in the heart of the application.

If there is no significant business rules it's hard to find reasons moving such logic into domain layer. In such cases having only application services look enough.

A Few Final Thoughts

So, what’s the difference then? It's still a tough question for me. But one guideline seems clear: domain services should encapsulate significant business rules. The more essential a piece of logic is to the business, the more it navigates toward the domain layer. Conversely, if the logic is less central to the business or mainly coordinates processes, it tends to the application layer. However, this distinction isn’t always straightforward.

At times, I question whether creating domain services is even necessary. In such cases, Clean Architecture principles can be appealing. Instead of debating which layer a piece of logic belongs to, Clean Architecture offers a clear answer: it should be placed within a use case.

Additional Resources

If you’d like to dive deeper into these topics, here are some useful references:

  1. Domain-Driven Design by Eric Evans (Chapter Five: A Model Expressed in Software – Services)
  2. Clean Architecture by Robert C. Martin (Part V: Architecture, Chapter 22: The Clean Architecture – Use Cases)
  3. Domain-Driven Design vs. Clean Architecture – Khalil Stemmler

This is my first post—hope you found it helpful! :)

AWS Q Developer image

Your AI Code Assistant

Automate your code reviews. Catch bugs before your coworkers. Fix security issues in your code. Built to handle large projects, Amazon Q Developer works alongside you from idea to production code.

Get started free in your IDE

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay