From rust-skills
Guides Rust domain modeling using DDD: entities, value objects, aggregates, repositories with ownership, invariants, and validation patterns.
npx claudepluginhub actionbook/rust-skills --plugin rust-skillsThis skill uses the workspace's default tool permissions.
> **Layer 2: Design Choices**
Provides Domain-Driven Design tactical patterns for modeling entities, value objects, domain services, repositories, aggregates, and bounded contexts in complex business domains.
Applies Domain-Driven Design to model software around business domains using bounded contexts, aggregates, ubiquitous language, domain events, and context mapping. For domain modeling, monolith splitting, and microservice boundaries.
Guides domain modeling with Rich Hickey's data-oriented and Scott Wlaschin's type-driven design principles. Contextualizes models, identifies inconsistencies, builds ubiquitous language, generates Mermaid/Graphviz/ASCII diagrams for types and business domains.
Share bugs, ideas, or general feedback.
Layer 2: Design Choices
What is this concept's role in the domain?
Before modeling in code, understand:
| 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 |
Before creating a domain type:
What's the concept's identity?
What invariants must hold?
Who owns this data?
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? |
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
| 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, ... } |
struct Email(String);
impl Email {
pub fn new(s: &str) -> Result<Self, ValidationError> {
validate_email(s)?;
Ok(Self(s.to_string()))
}
}
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
}
}
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
}
}
}
| 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 |
| When | See |
|---|---|
| Type-driven implementation | m05-type-driven |
| Ownership for aggregates | m01-ownership |
| Domain error handling | m13-domain-error |
| Specific domain rules | domain-* |