implement-value-object
npx skills add https://github.com/dawiddutoit/custom-claude --skill implement-value-object
Agent 安装分布
Skill 文档
Works with Python dataclasses in domain/value_objects/ and domain/values/ directories.
implement-value-object
Purpose
Create immutable domain value objects using the frozen dataclass pattern with proper validation, factory methods, and immutability guarantees enforced at the type system level.
When to Use
Use this skill when:
- Implementing domain value objects – Creating validated, immutable domain concepts
- Creating immutable data structures – Building type-safe value containers
- Adding validation to values – Ensuring domain constraints are enforced
Trigger phrases:
- “Create a value object for X”
- “Implement immutable Y value”
- “Add validation to Z value”
- “Build value object with validation”
Quick Start
Create immutable domain value objects using the frozen dataclass pattern with proper validation, factory methods, and immutability guarantees enforced at the type system level.
Most common use case:
For a simple validated value object:
from dataclasses import dataclass
@dataclass(frozen=True)
class EmailAddress:
"""Immutable email address value object."""
value: str
def __post_init__(self) -> None:
"""Validate email format."""
if not self.value:
raise ValueError("Email address cannot be empty")
if "@" not in self.value:
raise ValueError(f"Invalid email format: {self.value}")
Table of Contents
Core Sections
- Purpose – Immutable domain value objects with frozen dataclass pattern
- Instructions – Complete implementation guide
- Step 1: Define Frozen Dataclass – Basic immutable structure
- Step 2: Add Validation in post_init – Validation while respecting immutability
- Step 3: Add Factory Methods – Convenient constructors for different input types
- Step 4: Add String Representations – Useful string representations
- Step 5: Add Domain Behavior – Methods expressing domain operations
- Step 6: Add Type Hints and Documentation – Full type safety
- Examples – 4 production-ready patterns
- Example 1: Simple String Value Object – Basic validation pattern
- Example 2: Path Value Object with Normalization – Type coercion in frozen context
- Example 3: Hash Value Object with Factory – Computing values from content
- Example 4: Multi-Field Value Object with Validation – Complex constraints
Advanced Topics
- Requirements – Dependencies and environment setup
- Common Patterns – Reusable implementation patterns
- Pattern: Computed Properties – Derived values with @property
- Pattern: Type Coercion in Frozen Context – Ensuring type consistency
- Pattern: Validation with Custom Errors – Clear error messages
- Pattern: Serialization – Dictionary conversion support
- Testing Value Objects – Test strategies for validation, immutability, equality, factories, serialization
- File Locations – Domain layer placement conventions
- See Also – Templates, examples, architecture references
Utility Scripts
- Convert to Value Object – Analyze codebase to suggest value object candidates
- Generate Value Object – Generate value objects with validation, factory methods, and tests
- Validate Value Objects – Validate value object patterns in the codebase
Purpose
Create immutable domain value objects using the frozen dataclass pattern with proper validation, factory methods, and immutability guarantees enforced at the type system level.
Instructions
Step 1: Define Frozen Dataclass
Create the basic immutable structure:
from dataclasses import dataclass
@dataclass(frozen=True)
class YourValueObject:
"""Immutable value object representing [domain concept].
Encapsulates [what logic/behavior].
"""
value: str # Primary value
# Optional additional fields for metadata
Key points:
- Always use
@dataclass(frozen=True)for immutability - Add comprehensive docstring explaining domain meaning
- Use
valueas primary field name for simple value objects - All fields are immutable after construction
Step 2: Add Validation in post_init
Validate constraints while respecting immutability:
def __post_init__(self) -> None:
"""Validate value object constraints."""
# Validation checks (raise ValueError on failure)
if not self.value:
raise ValueError("Value cannot be empty")
# For type coercion or normalization in frozen context:
if not isinstance(self.value, ExpectedType):
object.__setattr__(self, "value", ExpectedType(self.value))
# For normalization (e.g., path resolution):
object.__setattr__(self, "value", self.normalize(self.value))
CRITICAL: Modifying Frozen Dataclass Fields
Since frozen=True prevents normal attribute assignment, use object.__setattr__() to modify fields during __post_init__:
# â WRONG - Will raise FrozenInstanceError
def __post_init__(self) -> None:
self.value = self.value.lower() # FAILS with frozen=True
# â
CORRECT - Use object.__setattr__()
def __post_init__(self) -> None:
object.__setattr__(self, "value", self.value.lower())
Validation patterns:
- Empty checks:
if not self.value: raise ValueError(...) - Format validation:
if not re.match(pattern, self.value): raise ValueError(...) - Type coercion:
object.__setattr__(self, "field", Type(field)) - Normalization:
object.__setattr__(self, "value", normalized_value)
Step 3: Add Factory Methods
Provide convenient constructors for different input types:
@classmethod
def from_string(cls, value_str: str) -> "YourValueObject":
"""Create from string representation.
Args:
value_str: String to parse
Returns:
YourValueObject instance
"""
return cls(value=value_str)
@classmethod
def from_dict(cls, data: dict) -> "YourValueObject":
"""Create from dictionary representation.
Args:
data: Dictionary with value object data
Returns:
YourValueObject instance
"""
return cls(value=data["value"])
@classmethod
def from_content(cls, content: str) -> "YourValueObject":
"""Create from raw content (e.g., compute hash).
Args:
content: Raw content to process
Returns:
YourValueObject instance
"""
# Process content (e.g., hash it)
processed = process(content)
return cls(value=processed)
Common factory method patterns:
from_string()– Parse string representationfrom_dict()– Deserialize from dictionaryfrom_content()– Compute value from content (hashes)from_components()– Build from multiple partsfrom_bytes()– Create from binary data
Step 4: Add String Representations
Provide useful string representations:
def __str__(self) -> str:
"""User-friendly string representation.
Returns:
The value as a string
"""
return str(self.value)
def __repr__(self) -> str:
"""Developer-friendly representation.
Returns:
String showing class and value
"""
return f"YourValueObject('{self.value}')"
For long values (like hashes), truncate in repr:
def __repr__(self) -> str:
"""Developer-friendly representation."""
if len(self.value) > 8:
return f"FileHash('{self.value[:8]}...')"
return f"FileHash('{self.value}')"
Step 5: Add Domain Behavior
Add methods that express domain operations:
@property
def short_version(self) -> str:
"""Get shortened version for display."""
return self.value[:8]
def matches_pattern(self, pattern: str) -> bool:
"""Check if value matches a pattern."""
import fnmatch
return fnmatch.fnmatch(self.value, pattern)
def to_dict(self) -> dict:
"""Convert to dictionary representation."""
return {
"value": self.value,
# Include computed properties
"short": self.short_version,
}
Common domain behaviors:
- Comparison operations (equality is automatic with dataclass)
- Pattern matching
- Format conversion
- Computed properties
- Dictionary serialization
Step 6: Add Type Hints and Documentation
Ensure full type safety:
from typing import Optional
@dataclass(frozen=True)
class ComplexValueObject:
"""Detailed docstring."""
value: str
metadata: Optional[str] = None # Optional fields need defaults
def operation(self, param: str) -> "ComplexValueObject":
"""Return type hints use string for forward reference."""
# Operations that return new instances (immutability)
return ComplexValueObject(value=f"{self.value}:{param}")
Examples
See examples/detailed-examples.md for comprehensive examples including:
- Simple string value objects with validation
- Path value objects with normalization and type coercion
- Hash value objects with factory methods
- Multi-field value objects with complex validation
Requirements
- Python 3.10+ (for
dataclassesand type union syntaxX | None) - No external dependencies for basic value objects
- Optional:
xxhashfor fast hashing (uv pip install xxhash)
Common Patterns
Pattern: Computed Properties
Use @property for derived values:
@property
def short_id(self) -> str:
"""Get shortened version of identity."""
parts = self.value.split(":")
return parts[-1] if parts else self.value
Pattern: Type Coercion in Frozen Context
When you need to ensure type consistency:
def __post_init__(self) -> None:
"""Ensure value is correct type."""
if not isinstance(self.value, Path):
object.__setattr__(self, "value", Path(self.value))
Pattern: Validation with Custom Errors
Provide clear error messages:
def __post_init__(self) -> None:
"""Validate with descriptive errors."""
if not self.value:
raise ValueError(f"{self.__class__.__name__} value cannot be empty")
if len(self.value) < 3:
raise ValueError(f"{self.__class__.__name__} must be at least 3 characters")
Pattern: Serialization
Support dictionary conversion:
def to_dict(self) -> dict:
"""Convert to dictionary for serialization."""
return {
"value": self.value,
# Include any metadata or computed properties
}
@classmethod
def from_dict(cls, data: dict) -> "YourValueObject":
"""Deserialize from dictionary."""
return cls(value=data["value"])
Testing Value Objects
Value objects should be tested for:
- Validation: Ensure invalid inputs raise
ValueError - Immutability: Verify frozen behavior
- Equality: Test value-based equality (automatic with dataclass)
- Factory methods: Test all construction paths
- Serialization: Test to_dict/from_dict round-trip
def test_value_object_validation():
"""Test validation raises on invalid input."""
with pytest.raises(ValueError, match="cannot be empty"):
YourValueObject(value="")
def test_value_object_immutability():
"""Test frozen behavior."""
obj = YourValueObject(value="test")
with pytest.raises(FrozenInstanceError):
obj.value = "changed"
def test_factory_method():
"""Test factory method construction."""
obj = YourValueObject.from_string("test")
assert obj.value == "test"
def test_serialization_round_trip():
"""Test to_dict/from_dict round-trip."""
obj = YourValueObject(value="test")
data = obj.to_dict()
restored = YourValueObject.from_dict(data)
assert restored == obj
File Locations
Value objects belong in the domain layer:
- Simple value objects:
src/project_watch_mcp/domain/value_objects/ - Complex value objects:
src/project_watch_mcp/domain/values/
Naming convention: {concept_name}.py (e.g., file_path.py, identity.py)
See Also
- templates/value-object-template.py – Starter template
- ARCHITECTURE.md – Domain layer patterns