Domain Modeling
Layer 2: Design Choices
Core Question
What is this concept's role in the domain?
Before modeling in code, understand:
-
Is it an Entity (identity matters) or Value Object (interchangeable)?
-
What invariants must be maintained?
-
Where are the aggregate boundaries?
Domain Concept → Rust Pattern
Domain Concept Rust Pattern Ownership Implication
Entity struct + Id Owned, unique identity
Value Object struct + Clone/Copy Shareable, immutable
Aggregate Root struct owns children Clear ownership tree
Repository trait Abstracts persistence
Domain Event enum Captures state changes
Service impl block / free fn Stateless operations
Thinking Prompt
Before creating a domain type:
What's the concept's identity?
-
Needs unique identity → Entity (Id field)
-
Interchangeable by value → Value Object (Clone/Copy)
What invariants must hold?
-
Always valid → private fields + validated constructor
-
Transition rules → type state pattern
Who owns this data?
-
Single owner (parent) → owned field
-
Shared reference → Arc/Rc
-
Weak reference → Weak
Trace Up ↑
To domain constraints (Layer 3):
"How should I model a Transaction?" ↑ Ask: What domain rules govern transactions? ↑ Check: domain-fintech (audit, precision requirements) ↑ Check: Business stakeholders (what invariants?)
Design Question Trace To Ask
Entity vs Value Object domain-* What makes two instances "the same"?
Aggregate boundaries domain-* What must be consistent together?
Validation rules domain-* What business rules apply?
Trace Down ↓
To implementation (Layer 1):
"Model as Entity" ↓ m01-ownership: Owned, unique ↓ m05-type-driven: Newtype for Id
"Model as Value Object" ↓ m01-ownership: Clone/Copy OK ↓ m05-type-driven: Validate at construction
"Model as Aggregate" ↓ m01-ownership: Parent owns children ↓ m02-resource: Consider Rc for shared within aggregate
Quick Reference
DDD Concept Rust Pattern Example
Value Object Newtype struct Email(String);
Entity Struct + ID struct User { id: UserId, ... }
Aggregate Module boundary mod order { ... }
Repository Trait trait UserRepo { fn find(...) }
Domain Event Enum enum OrderEvent { Created, ... }
Pattern Templates
Value Object
struct Email(String);
impl Email { pub fn new(s: &str) -> Result<Self, ValidationError> { validate_email(s)?; Ok(Self(s.to_string())) } }
Entity
struct UserId(Uuid);
struct User { id: UserId, email: Email, // ... other fields }
impl PartialEq for User { fn eq(&self, other: &Self) -> bool { self.id == other.id // Identity equality } }
Aggregate
mod order { pub struct Order { id: OrderId, items: Vec<OrderItem>, // Owned children // ... }
impl Order {
pub fn add_item(&mut self, item: OrderItem) {
// Enforce aggregate invariants
}
}
}
Common Mistakes
Mistake Why Wrong Better
Primitive obsession No type safety Newtype wrappers
Public fields with invariants Invariants violated Private + accessor
Leaked aggregate internals Broken encapsulation Methods on root
String for semantic types No validation Validated newtype
Related Skills
When See
Type-driven implementation m05-type-driven
Ownership for aggregates m01-ownership
Domain error handling m13-domain-error
Specific domain rules domain-*