IDomainService vs IApplicationService in DDDplus: Layered Architecture Guide
In the DDDplus framework, IApplicationService orchestrates use-case workflows and coordinates domain objects, while IDomainService encapsulates pure business logic that cannot be assigned to entities or value objects.
The cp-ddd-framework (DDDplus) implements strict architectural boundaries between the application and domain layers through marker interfaces. Understanding the distinction between IDomainService and IApplicationService ensures business logic remains isolated from orchestration concerns, maintaining the integrity of your Domain-Driven Design architecture.
Architectural Layer Responsibilities
DDDplus enforces a clear separation between the horizontal application layer and the vertical domain layer. This separation is codified in the framework's specification module through two distinct marker interfaces.
The Application Layer Contract
Located in dddplus-spec/src/main/java/io/github/dddplus/model/IApplicationService.java, this interface represents the application layer's entry point. The source code documentation explicitly states: "应用层,负责组织业务场景,编排业务,隔离场景对领域层的差异" (Application layer, responsible for organizing business scenarios, orchestrating business, and isolating scenario differences from the domain layer).
IApplicationService implementations handle:
- Transaction management and security concerns
- Input validation and DTO mapping
- Coordination of multiple domain objects and services
- Driving the overall use-case workflow
The Domain Layer Contract
Defined in dddplus-spec/src/main/java/io/github/dddplus/model/IDomainService.java, this interface marks pure domain logic. The interface documentation clarifies: "只负责业务规则,不负责业务流程,业务流程由 IApplicationService 负责" (Only responsible for business rules, not business process; business process is handled by IApplicationService).
IDomainService implementations contain:
- Complex business calculations and algorithms
- Validation logic that spans multiple aggregates
- Business rules that cannot naturally belong to a single entity or value object
- Pure logic independent of the orchestrating use-case
Key Differences
Understanding the conceptual boundary between these interfaces prevents architectural leakage:
-
Orchestration vs. Implementation:
IApplicationServiceanswers "what to do" by coordinating the workflow and delegating to appropriate domain objects;IDomainServiceanswers "how to do it" by implementing the specific business behavior. -
Layer Awareness: Application services understand the surrounding context, external APIs, and presentation concerns; domain services operate within a bounded context without knowledge of the triggering scenario.
-
Transaction Boundaries: Application services define transaction scopes and handle infrastructure concerns; domain services focus exclusively on business invariants and calculations.
Implementation Examples
Defining an Application Service
Application services implement IApplicationService and focus on workflow coordination:
package com.example.app.service;
import io.github.dddplus.model.IApplicationService;
import org.springframework.stereotype.Service;
import com.example.domain.OrderDomainService;
@Service
public class OrderAppService implements IApplicationService {
private final OrderDomainService domainService;
public OrderAppService(OrderDomainService domainService) {
this.domainService = domainService;
}
/** Application-level workflow: validates input → delegates to domain → persists result */
public void placeOrder(PlaceOrderCmd cmd) {
// 1️⃣ Validation / transaction handling (application concerns)
// 2️⃣ Delegate core business rule to the domain service
domainService.processOrder(cmd.getOrderId(), cmd.getItems());
// 3️⃣ Additional post-processing, notifications, etc.
}
}
Defining a Domain Service
Domain services implement IDomainService and contain only business-centric logic:
package com.example.domain;
import io.github.dddplus.model.IDomainService;
import org.springframework.stereotype.Component;
import com.example.model.Order;
import com.example.repository.OrderRepository;
@Component
public class OrderDomainService implements IDomainService {
private final OrderRepository repository;
public OrderDomainService(OrderRepository repository) {
this.repository = repository;
}
/** Pure domain rule – calculate total & enforce invariants */
public void processOrder(String orderId, List<Item> items) {
Order order = repository.findById(orderId);
order.calculateTotal(items);
order.checkBusinessConstraints(); // domain-specific validation
repository.save(order);
}
}
Wiring Layers Together
Controllers interact exclusively with the application layer:
@RestController
@RequestMapping("/orders")
public class OrderController {
private final OrderAppService appService;
public OrderController(OrderAppService appService) {
this.appService = appService;
}
@PostMapping
public ResponseEntity<Void> create(@RequestBody PlaceOrderCmd cmd) {
appService.placeOrder(cmd); // Application layer entry point
return ResponseEntity.ok().build();
}
}
Framework Enforcement
DDDplus provides compile-time validation to prevent layer violations. The ArchitectureEnforcer class in dddplus-enforce/src/main/java/io/github/dddplus/ArchitectureEnforcer.java analyzes dependencies to ensure domain services remain free of application concerns.
Additionally, the @DomainService annotation in dddplus-runtime/src/main/java/io/github/dddplus/annotation/DomainService.java marks concrete implementations for framework processing and validation.
Summary
-
IApplicationServiceorchestrates use-cases, manages transactions, and coordinates domain objects within the application layer. -
IDomainServiceencapsulates pure business logic, complex calculations, and cross-aggregate business rules within the domain layer. -
The separation is enforced at compile-time by
ArchitectureEnforcerto maintain strict architectural boundaries as defined indddplus-spec.
Frequently Asked Questions
Can an Application Service call another Application Service?
While technically possible, this pattern indicates a missing domain concept or improper layer assignment. Application services should compose domain services rather than other application services to keep the domain layer rich and the application layer thin.
Should Domain Services access Repositories?
Yes, domain services may access repositories to load aggregates required for business operations, as demonstrated in the OrderDomainService example. However, they should remain unaware of transaction boundaries and persistence details, which are application layer concerns.
How does DDDplus prevent mixing these layers?
The framework uses ArchitectureEnforcer to perform static analysis at compile time, detecting improper dependencies such as application logic leaking into domain services or domain services depending on application layer components.
When should logic reside in a Domain Service versus an Entity?
Place logic in a domain service when it involves multiple aggregates, requires external business rules, or does not naturally belong to a single entity. Keep logic in entities when it modifies the entity's own state and enforces local invariants.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →