Hydrators Service Kind
Overview
Hydrators are entity-specific services responsible for populating related data into domain models. They manage the loading of relationships and nested data structures, providing different presets for various use cases like list views, detail views, and API responses.
Architecture
Core Components
Base Hydrator Library (src/lib/hydrator/)
- Hydrator struct: Core hydration coordinator
- HydratorMod: Configuration object containing hydration paths and options
- HydratingPath: Represents specific relationship paths to load
Entity-Specific Hydrators (src/srv/hydrators/)
- Each domain model has its own dedicated hydrator (e.g., GardenHydrator, UserHydrator)
- All hydrators follow the same interface pattern and method conventions
- Integrated with corresponding Relationer services for actual data loading
Key Structures
HydratorMod
Configuration object that controls hydration behavior:
- Paths: Array of HydratingPath objects specifying which relationships to load
- Contains(): Method to check if a specific relationship path should be loaded
HydratingPath
Represents a relationship loading specification:
- Path: Array of strings defining the relationship chain to hydrate
- Supports nested relationship loading (e.g., ["garden", "plantations", "plants"])
Standard Methods
Every entity hydrator implements these standard methods:
Core Hydration Methods
Many(ctx, entities, mod) error // Hydrate multiple entities
One(ctx, entity, mod) error // Hydrate single entity
Preset-Based Methods
OneViaPreset(ctx, entity, preset, mod) error // Hydrate single entity with preset
MustOneViaPreset(ctx, entity, preset, mod) // Hydrate with panic on error
ManyViaPreset(ctx, entities, preset, mod) error // Hydrate multiple entities with preset
MustManyViaPreset(ctx, entities, preset, mod) // Hydrate multiple with panic on error
Internal Methods
presetSwitch(preset, mod) // Apply preset configuration to mod
showPreset(mod) // Configure mod for detail views
listPreset(mod) // Configure mod for list views
oneRelations(mod) // Add single-entity relationships
selfRelations(mod) // Add self-contained relationships
Usage Examples
Basic Entity Hydration
// Hydrate specific relationships
mod := &hydrator.HydratorMod{}
mod.AddHydratingPath("garden_tasks")
mod.AddHydratingPath("plantations")
err := gardenHydrator.One(ctx, garden, mod)
Preset-Based Hydration
// Use predefined hydration preset
err := gardenHydrator.OneViaPreset(ctx, garden, "show", nil)
// Hydrate multiple entities for list display
err := gardenHydrator.ManyViaPreset(ctx, gardens, "list", nil)
Custom Hydration Paths
mod := &hydrator.HydratorMod{}
mod.AddHydratingPath("plantations", "plants") // Nested relationship
mod.AddHydratingPath("garden_tasks", "assignee") // Deep relationship
err := gardenHydrator.One(ctx, garden, mod)
Hydration Presets
Hydrators support predefined hydration presets for common use cases:
Standard Presets
- “show”: Detail view hydration - loads comprehensive related data for entity display
- “list”: List view hydration - loads minimal related data for efficient list rendering
Preset Configuration
Each preset configures the HydratorMod with appropriate relationship paths:
func (this *GardenHydrator) showPreset(mod *hydrator.HydratorMod) {
this.oneRelations(mod) // Load detail-specific relationships
}
func (this *GardenHydrator) listPreset(mod *hydrator.HydratorMod) {
this.selfRelations(mod) // Load list-optimized relationships
}
Integration with Relationers
Hydrators delegate actual data loading to Relationer services:
- Use EntityRelationer.HydrateRelationshipName() methods for loading related data
- Manage the orchestration of multiple relationship loads
- Handle conditional loading based on HydratorMod configuration
Relationship Loading Logic
Hydrators use conditional loading based on the HydratorMod configuration:
func (this *GardenHydrator) Many(ctx context.Context, entities []*mdl.Garden, mod *hydrator.HydratorMod) error {
if mod.Contains("garden_tasks") {
this.GardenRelationer.HydrateGardenTasks(ctx, entities, nil)
}
if mod.Contains("plantations") {
this.GardenRelationer.HydratePlantations(ctx, entities, nil)
}
return nil
}
Error Handling
- Standard methods return errors for caller handling
- Must* methods panic on errors (using
lib.Poe()) - Relationship loading errors bubble up from Relationists
- Failed hydrations don’t modify the original entities
Performance Considerations
- Selective Loading: Only loads explicitly requested relationships
- Batch Loading:
Many()methods optimize for loading relationships across multiple entities - Preset Optimization: Presets are tuned for specific use cases to minimize over-fetching
- Conditional Logic: Avoids unnecessary database queries when relationships aren’t requested