Expert model design decisions for iOS/tvOS: when DTO separation adds value vs overkill, validation strategy selection, immutability trade-offs, and custom Codable decoder design. Use when designing data models, implementing API contracts, or debugging decoding failures. Trigger keywords: Codable, DTO, domain model, CodingKeys, custom decoder, validation, immutable, struct, mapping, JSON decoding
View on GitHubKaakati/rails-enterprise-dev
reactree-ios-dev
January 25, 2026
Select agents to install to:
npx add-skill https://github.com/Kaakati/rails-enterprise-dev/blob/main/plugins/reactree-ios-dev/skills/model-patterns/SKILL.md -a claude-code --skill model-patternsInstallation paths:
.claude/skills/model-patterns/# Model Patterns — Expert Decisions
Expert decision frameworks for model design choices. Claude knows Codable syntax — this skill provides judgment calls for when to separate DTOs, validation strategies, and immutability trade-offs.
---
## Decision Trees
### DTO vs Single Model
```
Does API response match your domain needs?
├─ YES (1:1 mapping)
│ └─ Is API contract stable?
│ ├─ YES → Single Codable model is fine
│ └─ NO → DTO protects against API changes
│
├─ NO (needs transformation)
│ └─ DTO + Domain model
│ DTO: matches API exactly
│ Domain: matches app needs
│
└─ Multiple APIs for same domain concept?
└─ Separate DTOs per API
Single domain model aggregates
```
**The trap**: DTO for everything. If your API matches your domain and is stable, a single Codable struct is simpler. Add DTO layer when it solves a real problem.
### Validation Strategy Selection
```
When should validation happen?
├─ External data (API, user input)
│ └─ Validate at boundary (init or factory)
│ Fail fast with clear errors
│
├─ Internal data (already validated)
│ └─ Trust it (no re-validation)
│ Validation at boundary is sufficient
│
└─ Critical invariants (money, permissions)
└─ Type-level enforcement
Email type, not String
Money type, not Double
```
### Struct vs Class Decision
```
What are your requirements?
├─ Simple data container
│ └─ Struct (value semantics)
│ Passed by copy, immutable by default
│
├─ Shared mutable state needed?
│ └─ Really? Reconsider design
│ └─ If truly needed → Class with @Observable
│
├─ Identity matters (same instance)?
│ └─ Class (reference semantics)
│ But consider if ID equality suffices
│
└─ Inheritance needed?
└─ Class (but prefer composition)
```
### Custom Decoder Complexity
```
How much custom decoding?
├─ Just key mapping (snake_case → camelCase)
│ └─ Use keyDecodingStrategy
│ decoder.keyDecodingStrategy = .convertFromSnakeCase
│
├─ Few fields need transformation
│