m09-domain
actionbook/rust-skills
Model domain concepts as Entities, Value Objects, and Aggregates using Rust ownership and type safety.
What is m09-domain?
Domain Modeling teaches how to translate domain-driven design (DDD) concepts into Rust patterns. Use this when designing core business types—deciding whether something is an Entity (unique identity), Value Object (interchangeable), or Aggregate (ownership hierarchy)—and enforcing invariants through type safety and private fields.
- Distinguish Entities (unique identity) from Value Objects (interchangeable by value) and choose appropriate Rust patterns for each
- Define Aggregate boundaries and model ownership hierarchies using Rust's ownership system
- Enforce domain invariants through private fields, validated constructors, and type-state patterns
- Map DDD concepts (Repository, Domain Event, Service) to Rust traits, enums, and impl blocks
- Avoid primitive obsession by wrapping semantic types in newtypes with validation
How to install m09-domain
npx skills add https://github.com/actionbook/rust-skills --skill m09-domainHow to use m09-domain
- 1.Identify the core concept you're modeling and ask: does it need unique identity or is it interchangeable by value?
- 2.For Entities, create a struct with an Id field and implement identity-based equality (PartialEq on id only)
- 3.For Value Objects, use newtypes or structs with Clone/Copy and validation in a constructor
- 4.Define invariants and enforce them through private fields and methods on the aggregate root
- 5.Use module boundaries to encapsulate aggregates and prevent direct access to internal children
Use cases
- Designing a User entity with unique identity and Email value object with validation
- Modeling an Order aggregate that owns OrderItems and enforces consistency rules
- Creating a Repository trait to abstract persistence from domain logic
- Building a domain event enum to capture state transitions (e.g., OrderCreated, OrderShipped)
- Implementing type-safe IDs using newtypes instead of raw strings or integers
- Backend engineers building domain-driven systems
- Architects designing layered Rust applications
- Teams establishing domain model patterns and invariants
- Developers transitioning from DDD theory to Rust implementation
m09-domain FAQ
Use an Entity when two instances with different identities are considered different (e.g., User with UserId). Use a Value Object when instances are interchangeable by their attributes (e.g., Email, Money). Ask: 'Do two instances with the same data represent the same thing?'
Make fields private, provide a validated constructor (new method returning Result), and expose only methods that maintain invariants. For example, a BankAccount should not allow negative balance—only withdrawal methods that check the invariant.
An Aggregate is a domain concept (a cluster of entities and value objects that must be consistent). In Rust, you typically model it as a module boundary with a root struct that owns children, preventing external code from breaking invariants.
Prefer owned fields (parent owns children). Use Rc/Arc only if truly necessary for shared references within the aggregate. Generally, keep ownership clear and hierarchical.
Define a trait with methods like find(id) → Result<T>, save(entity) → Result<()>. This abstracts persistence details from domain logic, letting you swap implementations (in-memory, database, etc.).
Full instructions (SKILL.md)
Source of truth, from actionbook/rust-skills.
name: m09-domain description: "CRITICAL: Use for domain modeling. Triggers: domain model, DDD, domain-driven design, entity, value object, aggregate, repository pattern, business rules, validation, invariant, 领域模型, 领域驱动设计, 业务规则" user-invocable: false
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-* |
Related skills
More from actionbook/rust-skills and the wider catalog.

m10-performance
Measure, identify bottlenecks, then optimize with data structures, allocation strategies, and parallelism.

m11-ecosystem
Navigate Rust crate selection, dependency management, and ecosystem integration decisions.

m12-lifecycle
Design resource creation, usage, and cleanup patterns using RAII, Drop, and lifecycle strategies.

m13-domain-error
Design domain error handling with categorization, recovery strategies, and resilience patterns.

m14-mental-model
Mental models and analogies for understanding Rust ownership, borrowing, and core concepts.

m15-anti-pattern
Identify and refactor Rust code anti-patterns during review.