The Difference between Application Services and Domain Services in DDD

Application Service

In Domain-Driven Design (DDD), an application service is a service that represents a use case or operation in the application. It is typically implemented as a class that contains the application-specific business logic for performing a specific operation. For example, an application service might implement the logic for creating a new user account or processing an order.

Domain Service

A domain service, on the other hand, is a service that represents a generic business operation or capability that is not specific to a particular use case or application. It is typically implemented as a class that contains reusable business logic that can be used by multiple application services or other parts of the domain model. For example, a domain service might implement an algorithm for calculating shipping costs or a method for generating unique IDs.

In short, the main difference between application services and domain services is that application services are specific to a particular use case or operation in the application, while domain services are generic business capabilities that can be used across the domain model.

Show Me an Example!

Here's a very simple example to illustrate the difference between application and domain logic using javascript-like pseudocode. You might dislike JS, but I've always found it's simple and c-like syntax easy to read, even for absolute beginners :D

Here is an example of application logic:

// Define a function to calculate the interest on a savings account
function calculateInterest(principal, rate, time) {
  // Calculate the interest using the formula: principal * rate * time
  return principal * rate * time;
}

// Use the function to calculate the interest on a $1000 savings account
// with a 1% interest rate for 1 year
interest = calculateInterest(1000, 0.01, 1);

print(interest);
10

This is an example of domain logic:

// Define the minimum balance required for a savings account
const MINIMUM_BALANCE = 500;

// Define a function to check if a savings account is eligible for interest
function isEligibleForInterest(account) {
  // Check if the account balance is greater than the minimum balance required
  return account.balance > MINIMUM_BALANCE;
}

// Use the function to check if a savings account with a balance of $400 is eligible for interest
account = { balance: 400 };
print(isEligibleForInterest(account));
false

In this example, the calculateInterest() function is an example of application logic, because it defines a specific process for calculating interest on a savings account. The isEligibleForInterest() function, on the other hand, is an example of domain logic because it relies on business knowledge (in this case, the minimum balance required for a savings account) to determine if an account is eligible for interest.

Another example for a broadly applicable domain logic could look like this. It could be applied regardless of the account type (checking, savings, credit card, etc)

// Define the the balance required for closing an account
const ZERO_BALANCE = 0;

// Define a function to determine whether the conditions for closure are met
function isClosable(account) {
    return account.balance == ZERO_BALANCE;
}

account = { balance: -20 };

print(isClosable(account));
false

Here we simply use the isClosable() function to determine, if the business rules for closing an account are met. In this case, the account has a balance of -20, so the conditions are not met.

Confusing (application) state managers with state machines

A state manager, mostly used within UI frameworks is responsible for keeping track of state changes and updating any affected UI elements e.g. adding an item to a todo-list should visibly update the list or display errors to the user, if there were any.

A state machine on the other hand is responsible for handling predefined state transitions, ensuring that only valid transitions can be performed. These can either be defined in code or commonly used formats like YAML, JSÒN, XML etc.

The related code is usually encapsulated within a domain service. For the following example, there could be a PaymentService, with public facing methods like pay, cancel, refund.

Here is a simple example defined in yaml:

payment:
  states:
    - open
    - paid
    - refunded
    - cancelled
  init: open
  transitions:
    pay:
      from: open
      to: paid
    cancel:
      from: open
      to: cancelled
    refund:
      from: paid
      to: refunded

The defined transitions in the example above ensure that only valid transitions can be made. Everything else, e.g. trying to refund an open payment would either throw an exception, return an error object or whatever error logic your application/language is using.