Appearance
Application Service
Orchestrating domain operations to fulfill business use cases (DDD perspective)
Related Concepts: Use Cases | Domain Service | Clean Architecture
Note: At Synapse, we prefer the term Use Case over Application Service. This document explains the Domain-Driven Design perspective on application services for teams working in DDD contexts. For our standard approach, see Use Cases.
What is an Application Service?
In Domain-Driven Design terminology, an Application Service is a component in the application layer that orchestrates domain operations to fulfill business use cases. If you're familiar with Clean Architecture, this is what Uncle Bob Martin calls a Use Case or Use Case Interactor.
From Eric Evans' Domain-Driven Design:
"The application layer is responsible for ordering the notification. The domain layer is responsible for determining if a threshold was met... [Application services] are built on top of the populations of ENTITIES and VALUES, behaving like scripts that organize the potential of the domain to actually get something done."
Application Services are the thin orchestration layer that sits between your presentation/API layer and your domain model, coordinating domain objects to fulfill business operations without containing business logic themselves.
Use Cases vs Application Services
These are essentially the same pattern with different names from different architectural schools:
| Aspect | Use Cases (Clean Architecture) | Application Services (DDD) |
|---|---|---|
| Layer | Application/Use Case layer | Application layer |
| Purpose | Orchestrate business operations | Orchestrate domain objects |
| Dependencies | Interfaces (ports) | Repositories, domain services |
| Business Logic | May contain application-specific rules | Strictly no business logic |
| Terminology Origin | Robert C. Martin (Uncle Bob) | Eric Evans |
The Philosophical Distinction
The key difference is emphasis:
- Use Cases (Clean Architecture): Can contain "application-specific business rules" that coordinate entities
- Application Services (DDD): Contain no business logic at allpurely orchestration and delegation
As one practitioner explains: "Domain services hold domain logic whereas application services don't. Domain logic is everything that is related to business decisions."
In DDD, if you find business logic creeping into your Application Service, it's a signal to extract a Domain Service or push the behavior into an Entity or Value Object.
Application Layer Responsibilities
According to Eric Evans, the application layer:
"Defines the jobs the software is supposed to do and directs the expressive domain objects to work out problems. This layer is kept thin. It does not contain business rules or knowledge, but only coordinates tasks and delegates work to collaborations of domain objects in the next layer down."
What Application Services DO
- Orchestrate workflows - Coordinate domain objects, repositories, and domain services
- Manage transactions - Define transactional boundaries for use cases
- Handle input/output - Accept DTOs, return DTOs or view models
- Coordinate infrastructure - Call notification services, message queues, etc.
- Dispatch domain events - Publish events raised by domain operations
- Sequence operations - Ensure operations happen in the correct order
What Application Services DON'T DO
- Contain business logic - All domain decisions belong in the domain layer
- Make business decisions - They ask domain objects to decide
- Hold state - They're stateless coordinators (though may track workflow progress)
- Expose domain objects directly - They translate between domain and external representations
Example: Funds Transfer (from Eric Evans)
Evans provides a comprehensive example showing how Application Services orchestrate domain operations:
typescript
// Application Service - Pure orchestration
class FundsTransferApplicationService {
constructor(
private fundsTransferDomainService: FundsTransferDomainService,
private notificationService: NotificationService,
private eventPublisher: EventPublisher
) {}
async transferFunds(request: TransferFundsRequest): Promise<TransferResult> {
// 1. Digest input (parse XML, validate format, etc.)
const { fromAccountId, toAccountId, amount } = this.parseRequest(request);
// 2. Delegate to domain service for business logic
const result = await this.fundsTransferDomainService.transfer(
fromAccountId,
toAccountId,
amount
);
// 3. Listen for confirmation
if (result.isSuccess()) {
// 4. Decide to send notification using infrastructure
await this.notificationService.sendConfirmation(
result.transactionId,
fromAccountId,
toAccountId,
amount
);
// 5. Publish domain event for other systems
this.eventPublisher.publish(
new FundsTransferredEvent(result.transactionId)
);
}
// 6. Return result (likely to be converted to HTTP response)
return result;
}
private parseRequest(request: TransferFundsRequest): ParsedTransfer {
// Handle XML/JSON parsing, format validation
// This is application-level concern, not domain logic
}
}Notice the Application Service:
- Doesn't decide if the transfer is allowed (domain service does)
- Doesn't calculate debits and credits (domain service does)
- Does coordinate the workflow: parse, transfer, notify, publish
Compare this to the Domain Service which contains the actual business rules.
Three-Layer Service Pattern (from Eric Evans)
Evans illustrates how a single business capability spans three service types:
�������������������������������������������������������������
Application: Funds Transfer App Service
" Digest input (XML request)
" Send message to domain service
" Listen for confirmation
" Decide to send notification
��������������������,����������������������������������������
¼
�������������������������������������������������������������
Domain: Funds Transfer Domain Service
" Interact with Account and Ledger objects
" Make debits and credits
" Supply confirmation (allowed or not)
��������������������,����������������������������������������
¼
�������������������������������������������������������������
Infrastructure: Send Notification Service
" Send emails, letters, communications
" Technical delivery mechanism
�������������������������������������������������������������Example: Library Book Checkout
A practical example showing application-level orchestration:
typescript
// Application Service
class CheckoutBookApplicationService {
constructor(
private bookLendingService: BookLendingService, // Domain Service
private bookRepository: BookRepository,
private readerRepository: ReaderRepository,
private notificationService: NotificationService,
private unitOfWork: UnitOfWork
) {}
async execute(request: CheckoutBookRequest): Promise<CheckoutResult> {
// Application service coordinates but doesn't decide
const { readerId, bookId } = request;
// Start transaction boundary
await this.unitOfWork.begin();
try {
// Ask domain service to make the business decision
const loan = await this.bookLendingService.borrowBook(
readerId,
bookId
);
// Commit the transaction
await this.unitOfWork.commit();
// Handle side effects (infrastructure concerns)
await this.notificationService.sendLoanConfirmation(
loan.readerId,
loan.dueDate
);
// Return DTO (not domain object)
return CheckoutResult.success({
loanId: loan.id.value,
dueDate: loan.dueDate.toISOString(),
bookTitle: loan.book.title
});
} catch (error) {
await this.unitOfWork.rollback();
if (error instanceof BorrowingNotAllowedError) {
return CheckoutResult.failure(error.reason);
}
throw error;
}
}
}The Application Service:
- Manages the transaction boundary
- Calls the Domain Service (which contains business logic)
- Handles infrastructure concerns (notifications)
- Returns DTOs (not domain objects)
- Catches domain exceptions and translates them
Learn more about orchestration patterns at Sapiensworks
Key Characteristics
According to Khalil Stemmler's comparison:
- Stateless - Application Services don't hold state (unlike entities)
- Low complexity - Should have low cyclomatic complexity (minimal branching)
- Thin layer - Evans emphasizes keeping the application layer thin
- Single use case - Each service typically implements one business operation
- Framework-agnostic - No dependencies on web frameworks or ORMs
Common Patterns
Command Pattern
Many DDD practitioners use command objects with Application Services:
typescript
// Command (request DTO)
class TransferFundsCommand {
constructor(
public readonly fromAccountId: string,
public readonly toAccountId: string,
public readonly amount: number
) {}
}
// Application Service handles the command
class FundsTransferApplicationService {
async handle(command: TransferFundsCommand): Promise<TransferResult> {
// Orchestrate domain operations
}
}Domain Event Dispatching
Application Services often dispatch events raised by domain operations:
typescript
class CreateOrderApplicationService {
async execute(request: CreateOrderRequest): Promise<OrderCreatedResult> {
// Create order through domain
const order = Order.create(request.customerId, request.items);
await this.orderRepository.save(order);
// Dispatch domain events raised during creation
const events = order.getUncommittedEvents();
await this.eventDispatcher.dispatchAll(events);
return OrderCreatedResult.success(order.id);
}
}Learn more about event patterns at Developer20
Best Practices
DO
- Keep services thin - Delegate all business logic to domain layer
- One service per use case - Clear, focused responsibility
- Accept DTOs - Don't expose your API to domain objects directly
- Return DTOs or Result objects - Translate domain results for external clients
- Manage transactions - Define clear transactional boundaries
- Coordinate, don't decide - Ask domain objects to make decisions
DON'T L
- Put business logic in services - That belongs in domain layer
- Expose domain objects directly - Return DTOs or view models
- Mix presentation concerns - No HTTP, JSON, or UI logic
- Create fat services - If it's complex, you're doing domain work
- Skip domain layer - Don't go straight from app service to database
Synapse's Approach
At Synapse, we tend to prefer Use Cases over Application Services. We find the Clean Architecture terminology more accessible to stakeholders and clearer in intent. However, we follow the same principle: orchestrate domain operations without implementing business logic.
When reading DDD resources, understand that:
- Their "Application Services" are our "Use Cases"
- Their strict "no business logic" rule aligns with our "thin use cases" principle
- The patterns and techniques translate directly
For Synapse's standard approach, see:
Key Takeaways
- Application Services and Use Cases are the same pattern with different names
- DDD emphasizes strict separation - absolutely no business logic in Application Services
- They orchestrate, not decide - coordination without decision-making
- Three service types in DDD - Application, Domain, and Infrastructure services each have clear roles
- Transaction boundaries matter - Application Services define unit of work scope
- DTO translation layer - Bridge between external world and domain model
- Use the terminology that fits your context - DDD for domain-driven teams, Use Cases for broader clarity
Further Reading
Primary Sources
- Domain-Driven Design: Tackling Complexity in the Heart of Software - Eric Evans (2003) - Chapters on Application Layer and Services
- Clean Architecture - Robert C. Martin (2017) - Chapter 20: Business Rules
Supporting Articles
- DDD Application Services Explained - Mike Mogosanu's detailed explanation
- Domain services vs Application services - Vladimir Khorikov on the distinction
- Comparison of Domain-Driven Design and Clean Architecture - Khalil Stemmler's architectural comparison
- Services in DDD finally explained - Orchestration patterns in practice
- DDD - Are 'use cases' and 'application services' different names for the same thing? - Community discussion on Stack Exchange
- Unpacking the Layers of Clean Architecture - Layer responsibilities explained