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