docs/en/Community-Articles/2025-08-25-App-Services-vs-Domain-Services/POST.md
In ABP's layered architecture, we frequently encounter two types of services that appear similar but serve distinctly different purposes: Application Services and Domain Services. Understanding the differences between them is crucial for building clear and maintainable enterprise applications.
In ABP's layered architecture:
This layered design follows Domain-Driven Design (DDD) principles, ensuring clear separation of business logic and system maintainability.
Application Services are stateless services primarily used to implement application use cases. They act as a bridge between the presentation layer and domain layer, responsible for:
[Authorize] attribute or manual authorization checks via IAuthorizationServiceA standard application service method typically follows this pattern:
[Authorize(BookPermissions.Create)] // Declarative authorization
public virtual async Task<BookDto> CreateBookAsync(CreateBookDto input) // input is automatically validated
{
// Get related data
var author = await _authorRepository.GetAsync(input.AuthorId);
// Call domain service to execute business logic (if needed)
// You can also use the entity constructor directly if no complex business logic is required
var book = await _bookManager.CreateAsync(input.Title, author, input.Price);
// Persist changes
await _bookRepository.InsertAsync(book);
// Return DTO
return ObjectMapper.Map<Book, BookDto>(book);
}
It's worth mentioning that ABP also provides a special type of application service—Integration Services. They are application services marked with the [IntegrationService] attribute, designed for inter-module or inter-microservice communication.
We have a community article dedicated to integration services: Integration Services Explained — What they are, when to use them, and how they behave
Domain Services implement core business logic and are particularly needed when:
AssignToAsync) instead of generic names (e.g., UpdateAsync)public class IssueManager : DomainService
{
private readonly IRepository<Issue, Guid> _issueRepository;
public virtual async Task AssignToAsync(Issue issue, Guid userId)
{
// Business rule: Check user's unfinished task count
var openIssueCount = await _issueRepository.GetCountAsync(i => i.AssignedUserId == userId && !i.IsClosed);
if (openIssueCount >= 3)
{
throw new BusinessException("IssueTracking:ConcurrentOpenIssueLimit");
}
// Execute assignment logic
issue.AssignedUserId = userId;
issue.AssignedDate = Clock.Now;
}
}
| Dimension | Application Services | Domain Services |
|---|---|---|
| Layer Position | Application Layer | Domain Layer |
| Primary Responsibility | Use Case Orchestration | Business Logic Implementation |
| Data Interaction | DTOs | Domain Objects |
| Callers | Presentation Layer/Client Applications | Application Services/Other Domain Services |
| Authorization | Responsible for permission checks | No authorization logic |
| Transaction Management | Manages transaction boundaries (Unit of Work) | Participates in transactions but doesn't manage |
| Current User Context | Can access current user information | Should not depend on current user context |
| Return Types | Returns DTOs | Returns domain objects only |
| Query Operations | Can perform query operations | Should not define GET/query methods |
| Naming Convention | *AppService | *Manager or *Service |
In real-world development, these two types of services typically work together:
// Application Service
public class BookAppService : ApplicationService
{
private readonly BookManager _bookManager;
private readonly IRepository<Book> _bookRepository;
[Authorize(BookPermissions.Update)]
public virtual async Task<BookDto> UpdatePriceAsync(Guid id, decimal newPrice)
{
var book = await _bookRepository.GetAsync(id);
await _bookManager.ChangePriceAsync(book, newPrice);
await _bookRepository.UpdateAsync(book);
return ObjectMapper.Map<Book, BookDto>(book);
}
}
// Domain Service
public class BookManager : DomainService
{
public virtual async Task ChangePriceAsync(Book book, decimal newPrice)
{
// Domain service focuses on business rules
if (newPrice <= 0)
{
throw new BusinessException("Book:InvalidPrice");
}
if (book.IsDiscounted && newPrice > book.OriginalPrice)
{
throw new BusinessException("Book:DiscountedPriceCannotExceedOriginal");
}
if (book.Price == newPrice)
{
return;
}
// Additional business logic: Check if price change requires approval
if (await RequiresApprovalAsync(book, newPrice))
{
throw new BusinessException("Book:PriceChangeRequiresApproval");
}
book.ChangePrice(newPrice);
}
private Task<bool> RequiresApprovalAsync(Book book, decimal newPrice)
{
// Example business rule: Large price increases require approval
var increasePercentage = ((newPrice - book.Price) / book.Price) * 100;
return Task.FromResult(increasePercentage > 50); // 50% increase threshold
}
}
IBookAppService)GetAsync, CreateAsync, UpdateAsync, DeleteAsync)[Authorize] attribute for declarative authorization or manual checks via IAuthorizationServiceManager suffix for naming (e.g., BookManager)BusinessException with clear, unique error codes for domain validation failuresAssignToAsync, ChangePriceAsync)Application Services and Domain Services each have their distinct roles in the ABP framework: Application Services serve as use case orchestrators, handling authorization, validation, transaction management, and DTO transformations; Domain Services focus purely on business logic implementation without any infrastructure concerns. Integration Services are a special type of Application Service designed for inter-service communication.
Correctly understanding and applying these service patterns is key to building high-quality ABP applications. Through clear separation of responsibilities, we can not only build more maintainable code but also flexibly switch between monolithic and microservice architectures—this is precisely the elegance of ABP framework design.