BoundedDomainModel vs UnboundedDomainModel in DDDplus: Core Architecture Explained
In DDDplus, IUnboundedDomainModel defines the immutable core identity of an entity ("what it is"), while BoundedDomainModel<T> encapsulates scenario-specific behaviors and state ("what it does"), enabling role-based modeling that eliminates God-class anti-patterns through separation of concerns.
The funkygao/cp-ddd-framework implements a sophisticated domain-driven design architecture that separates core entity identity from contextual behaviors through BoundedDomainModel and UnboundedDomainModel. Understanding the fundamental differences between these two constructs is essential for implementing scenario-driven modeling and maintaining clean architecture in complex business domains.
Core Philosophy: Identity Versus Role
The architectural split between bounded and unbounded models addresses the fundamental DDD challenge of handling entities that participate in multiple business contexts without creating monolithic classes.
In dddplus-spec/src/main/java/io/github/dddplus/model/IUnboundedDomainModel.java, the unbounded model represents the core, immutable attributes of an entity—the answer to "what it is". This interface captures natural properties that remain constant regardless of business scenario, such as a user's identity or an order's creation timestamp.
Conversely, dddplus-spec/src/main/java/io/github/dddplus/model/BoundedDomainModel.java implements the Methodful Role pattern, representing scenario-specific views that answer "what it does". Each bounded model encapsulates behaviors, rules, and state relevant only within a particular bounded context, such as a user acting as a buyer in e-commerce versus a debtor in finance.
Technical Implementation in DDDplus Source Code
The framework provides distinct base types for each modeling approach in the dddplus-spec module.
Unbounded Domain Model Interface
Defined in dddplus-spec/src/main/java/io/github/dddplus/model/IUnboundedDomainModel.java, this interface marks the core entity. It contains no business logic specific to any use case, serving instead as the stable foundation upon which bounded models are built.
Bounded Domain Model Abstract Class
The BoundedDomainModel<T> class, located in dddplus-spec/src/main/java/io/github/dddplus/model/BoundedDomainModel.java, requires a generic type parameter T extending IUnboundedDomainModel. It maintains a reference to the underlying unbounded model via protected T model, allowing bounded implementations to access core entity attributes while adding scenario-specific methods.
Key Architectural Differences
The separation between these model types manifests across several critical dimensions:
Purpose and Semantics
- Unbounded: Captures universal identity and natural attributes shared across all scenarios.
- Bounded: Encapsulates role-specific behavior and mutable state for a single bounded context.
Lifecycle and Change Frequency
Unbounded models exhibit high stability and low change frequency, typically containing only natural keys and immutable properties. Bounded models are mutable and evolutionary, changing as business rules within their specific context evolve.
Cardinality and Relationships
A single unbounded model serves as the foundation for multiple bounded models. For example, a User entity can simultaneously support Buyer, Contact, and Debtor bounded models. Each bounded model holds a reference to its underlying unbounded instance via protected T model, establishing a clear ownership hierarchy.
Design Intent
This pattern prevents the God-class anti-pattern by ensuring orthogonal responsibilities remain separated. Rather than accumulating methods for every possible scenario into a single entity, DDDplus distributes scenario-specific logic across discrete bounded models.
Practical Implementation Examples
Example 1: User Entity with Multiple Bounded Roles
The following pattern demonstrates how a single unbounded entity supports multiple contextual roles:
// Core entity – shared across all contexts
class User implements IUnboundedDomainModel {
Buyer asBuyer() { return new Buyer(this); }
Contact asContact() { return new Contact(this); }
Debtor asDebtor() { return new Debtor(this); }
}
// Scenario 1 – e-commerce context
class Buyer extends BoundedDomainModel<User> {
void placeOrder() { /* … */ }
}
// Scenario 2 – social context
class Contact extends BoundedDomainModel<User> {
List<Friend> myFriends() { /* … */ }
void makeFriend(Contact whom) { /* … */ }
}
// Scenario 3 – finance context
class Debtor extends BoundedDomainModel<User> {
void loan(Money amount) { /* … */ }
void repay() { /* … */ }
}
The User class serves as the unbounded model containing core identity, while Buyer, Contact, and Debtor are distinct bounded models extending BoundedDomainModel<User>, each encapsulating scenario-specific behavior.
Example 2: Remote Context Integration
Bounded models also handle infrastructure concerns such as remote service integration:
class ShipmentOrderBag implements IUnboundedDomainModel {
// factory to obtain a remote-aware bounded model
ShipmentOrderBagContextRemote inContextOfRemote(ShipmentOrderGateway gateway) {
return new ShipmentOrderBagContextRemote(this, gateway);
}
}
// Bounded model that talks to a remote service
class ShipmentOrderBagContextRemote extends BoundedDomainModel<ShipmentOrderBag> {
private final ShipmentOrderGateway gateway;
ShipmentOrderBagContextRemote(ShipmentOrderBag model, ShipmentOrderGateway gateway) {
super(model);
this.gateway = gateway;
}
public ShipmentOrderBag acquireProductionLicence() {
// remote call, business-specific logic
return gateway.acquireLicense(this.model);
}
}
Here the unbounded ShipmentOrderBag remains pure domain logic, while the remote-specific integration logic is isolated in ShipmentOrderBagContextRemote, maintaining clean architecture boundaries.
Summary
- UnboundedDomainModel (
IUnboundedDomainModel) captures the immutable core identity of an entity—what it is—and remains stable across all business contexts with low change frequency. - BoundedDomainModel (
BoundedDomainModel<T>) encapsulates scenario-specific behaviors and mutable state—what it does—preventing God-class anti-patterns through role-based modeling. - The framework implements this pattern in
dddplus-spec/src/main/java/io/github/dddplus/model/, where bounded models maintain aprotected T modelreference to their unbounded counterparts. - This architectural separation enables multiple bounded contexts to evolve independently while sharing a common unbounded core, supporting both local domain logic and remote infrastructure integration without contaminating the domain layer.
Frequently Asked Questions
What is the primary purpose of separating bounded and unbounded models in DDDplus?
The separation prevents the accumulation of unrelated responsibilities into a single entity—the God-class anti-pattern—by isolating immutable core identity (IUnboundedDomainModel) from variable scenario-specific behaviors (BoundedDomainModel). This enables independent evolution of business contexts while maintaining a stable domain core.
How does BoundedDomainModel access the core entity properties?
Each BoundedDomainModel<T> maintains a protected reference to its underlying unbounded model via the protected T model field, established through the constructor super(model). This allows bounded implementations to access core identity attributes while extending functionality with context-specific methods.
Can a single unbounded model support multiple bounded models simultaneously?
Yes, this is the intended design pattern. A single IUnboundedDomainModel implementation—such as a User entity—can serve as the foundation for multiple BoundedDomainModel instances (e.g., Buyer, Contact, Debtor), each modeling a distinct role or bounded context without interfering with one another.
Where are IUnboundedDomainModel and BoundedDomainModel defined in the codebase?
The IUnboundedDomainModel interface and BoundedDomainModel abstract class are defined in dddplus-spec/src/main/java/io/github/dddplus/model/, specifically in IUnboundedDomainModel.java and BoundedDomainModel.java respectively. Unit tests illustrating usage patterns are available in dddplus-test/src/test/java/io/github/dddplus/model/BoundedDomainModelTest.java.
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 →