Accepted
2026-01-09
The project has configuration types for environment creation that sit at the boundary between external configuration sources (JSON files, CLI arguments) and the internal domain model. These types:
- Deserialize from JSON - Accept user input with raw primitives (
String,u32) - Validate and convert - Transform to strongly-typed domain objects via
to_*_config()methods - Generate JSON Schema - Derive
JsonSchemafor IDE autocomplete
The question arose: where should these types live in the DDD architecture?
Options considered:
- Domain layer (
src/domain/) - Where business entities live - Application layer (
src/application/) - Where use cases and commands live - Separate workspace package - For reuse by other applications
Additionally, AI agents need to reference these types to generate accurate configurations, as Rust types express constraints that JSON Schema cannot fully capture (e.g., NonZeroU32, tagged enums, custom validation logic).
Keep configuration DTOs in src/application/command_handlers/create/config/ (application layer).
Do not extract to a separate package at this time.
-
DTOs are not domain concepts - They represent input formats, not business entities. The domain layer should contain semantic business concepts like
Environment,TrackerConfig, not parsing concerns. -
Unidirectional dependency - Config DTOs import domain types to convert to (
DTO → Domain). This is correct: application layer depends on domain, not vice versa. -
Serialization is application concern - Heavy
serdeusage (Deserialize,Serialize,JsonSchema) belongs at application boundaries, not in the domain core. -
Command-specific - These types are specific to the
createcommand. Other commands may have different configuration needs. -
Follows Anti-Corruption Layer pattern - The config types protect the domain from external JSON format changes.
- No immediate consumers - No other Rust applications currently need these types
- Versioning overhead - Separate packages require versioning discipline
- Premature abstraction - YAGNI (You Aren't Gonna Need It)
- Types are well-organized - Already isolated in a submodule, easy to extract later
- Clear architectural boundaries - DTOs vs domain types are distinct
- AI agents can reference types - Folder path documented in
AGENTS.mdrule 19 - No versioning complexity - Single crate, simple dependency management
- Future-ready - Types are organized for potential extraction
- Cannot reuse from external Rust apps - Must copy types or depend on entire crate
- AI agents need to read Rust code - JSON schema alone is insufficient for full constraints
- If multiple applications need these types, extraction becomes necessary
- Changes to domain types may require updates to DTOs (but this is expected)
Rejected because:
- Domain types should represent business concepts, not input formats
- Would pollute domain with serialization concerns
- Domain already has clean types (
TrackerConfig,GrafanaConfig)
Deferred because:
- No current consumers
- Adds versioning complexity
- Can be done later if needed (types are already isolated)
Rejected because:
- Config parsing is not external system integration
- Would mix concerns with SSH, Ansible, OpenTofu adapters
- Secrecy Crate for Sensitive Data - How secrets are handled in DTOs vs domain
- DDD Layer Placement Guide - General guidance for layer decisions
- Anti-Corruption Layer pattern - Microsoft patterns documentation
- Config README - Detailed documentation for AI agents
- JSON Schema - Generated schema for IDE autocomplete