Skip to main content

implement-domain-types

Guide for implementing domain types (value objects and entities) with invariants in this Rust project. Covers the "always valid" pattern with private fields and validated constructors, custom Deserialize implementations to prevent bypass, and expressive error types with actionable help text. Use when creating or modifying domain value objects, entities, or types with business rules. Triggers on "domain type", "value object", "entity", "invariant", "validated constructor", "domain entity", "DDD practices", "domain model", or "implement domain".

소스 정보

저장소
torrust/torrust-tracker-deployer
최근 소스 활동
2026년 2월 25일 19:30
감지된 SKILL.md 언어
영어
스타
4
포크
2

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
implement-domain-types
description
Guide for implementing domain types (value objects and entities) with invariants in this Rust project. Covers the "always valid" pattern with private fields and validated constructors, custom Deserialize implementations to prevent bypass, and expressive error types with actionable help text. Use when creating or modifying domain value objects, entities, or types with business rules. Triggers on "domain type", "value object", "entity", "invariant", "validated constructor", "domain entity", "DDD practices", "domain model", or "implement domain".
metadata
{"author":"torrust","version":"1.0"}
# Implementing Domain Types with Invariants ## Core Rule: Domain Objects Are Always Valid After construction, a domain object must satisfy all its invariants. **Invalid objects must be impossible to create.** ## Pattern 1: Value Object with Private Fields + Validated Constructor ```rust // ✅ Correct pub struct EnvironmentName(String); // private field impl EnvironmentName { pub fn new(name: String) -> Result<Self, EnvironmentNameError> { if name.is_empty() { return Err(EnvironmentNameError::Empty); } if name.contains(char::is_uppercase) { return Err(EnvironmentNameError::UppercaseNotAllowed(name)); } Ok(Self(name)) } pub fn as_str(&self) -> &str { &self.0 } } ``` ```rust // ❌ Wrong - allows invalid state pub struct EnvironmentName { pub name: String, // public field, anyone can bypass validation } ``` ## Pattern 2: Custom Deserialize (Required for Serde) `#[derive(Deserialize)]` bypasses the constructor — **always implement custom `Deserialize`**: ```rust use serde::{Deserialize, Deserializer, Serialize}; #[derive(Debug, Clone, Serialize)] // Serialize can be derived pub struct HttpApiConfig { bind_address: SocketAddr, // private fields use_tls_proxy: bool, } #[derive(Deserialize)] struct HttpApiConfigRaw { // mirror struct for deserialization bind_address: SocketAddr, use_tls_proxy: bool, } impl<'de> Deserialize<'de> for HttpApiConfig { fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { let raw = HttpApiConfigRaw::deserialize(deserializer)?; HttpApiConfig::new(raw.bind_address, raw.use_tls_proxy) .map_err(serde::de::Error::custom) } } ``` ## Pattern 3: Expressive Error Types with Help Text ```rust #[derive(Debug, thiserror::Error)] pub enum HttpApiConfigError { #[error("Dynamic port (port 0) is not supported: {0}")] DynamicPortNotSupported(SocketAddr), #[error("TLS proxy requires a domain to be specified")] TlsProxyRequiresDomain, } impl HttpApiConfigError { #[must_use] pub fn help(&self) -> &'static str { match self { Self::DynamicPortNotSupported(_) => "Specify an explicit port number (e.g., 1212, 8080).", Self::TlsProxyRequiresDomain => "Set a domain name or disable TLS proxy.", } } } ``` ## Checklist - [ ] Fields are private (no `pub` on struct fields) - [ ] Validated constructor returns `Result<Self, Error>` - [ ] Custom `Deserialize` impl using a Raw mirror struct - [ ] Error type uses `thiserror::Error` with `help()` method - [ ] Single point of validation — not duplicated in setters/validators ## Reference Full guide: [`docs/contributing/ddd-practices.md`](../../docs/contributing/ddd-practices.md) ADR: [`docs/decisions/validated-deserialization-for-domain-types.md`](../../docs/decisions/validated-deserialization-for-domain-types.md)
GitHub에서 보기