Documentation

Service Layer Architecture

Overview

The service layer provides a comprehensive, standardized approach to handling all aspects of domain entity operations. Each entity receives a complete suite of specialized services that work together to handle different concerns.

Service Types

Every domain entity has these service types:

Data Services

  • Fetchers - Data retrieval with filtering, pagination, and querying
  • Handlers - CRUD operations with transaction support and validation
  • Tables - Database-to-entity mapping with ESO (Entity Storage Object) pattern

Transformation Services

  • Molders - Form-to-entity transformation with change detection
  • Serializers - Entity-to-JSON conversion for API responses
  • Stringers - Entity-to-string conversion for display purposes

Relationship Services

  • Hydrators - Relationship loading with preset configurations
  • Relationers - Batch relationship loading to prevent N+1 queries

Specialized Services

  • Searchers - Full-text search capabilities with indexing
  • Validators - Business rule validation
  • Policies - Access control and authorization

Service Architecture Pattern

Complete Service Suite

// Example for Garden entity
type GardenServices struct {
    // Core data services
    GardenFetcher    *fetchers.GardenFetcher
    GardenHandler    *handlers.GardenHandler  
    GardenTable      *tables.GardenTable
    
    // Transformation services
    GardenMolder     *molders.CreateGardenFormMolder
    GardenSerializer *serializers.GardenSerializer
    GardenStringer   *stringers.GardenStringer
    
    // Relationship services
    GardenHydrator   *hydrators.GardenHydrator
    GardenRelationer *relationers.GardenRelationer
    
    // Specialized services
    GardenSearcher   *searchers.GardenSearcher
    GardenValidator  *validators.GardenValidator
}

Service Interaction Patterns

Create Operation Flow

Form → Validator → Molder → Handler → Table → Database
                              ↓
                           Entity
                              ↓  
                         Serializer → JSON Response

Read Operation Flow

Query → Fetcher → Table → Entity → Hydrator → Relationer
                             ↓           ↓
                        Serializer  String Representation

Update Operation Flow

Form → Validator → Molder → Handler → Table → Database
  ↓                   ↓
Change Detection  Transaction Management

Service Implementation Standards

Interface Consistency

All services of the same type follow identical interface patterns:

// Fetcher interface pattern (example)
type EntityFetcher interface {
    FindAll(ctx context.Context, mod *fetcher.FetcherMod) ([]*Entity, error)
    FindOne(ctx context.Context, mod *fetcher.FetcherMod) (*Entity, bool, error)
    FindOneById(ctx context.Context, id string, mod *fetcher.FetcherMod) (*Entity, bool, error)
    FindPage(ctx context.Context, mod *fetcher.FetcherMod) ([]*Entity, *fetcher.FetcherPagination, error)
}

// Handler interface pattern (example)
type EntityHandler interface {
    Create(ctx context.Context, entity *Entity, mod *handler.HandlerMod) (*Entity, error)
    Update(ctx context.Context, entity *Entity, mod *handler.HandlerMod) (*Entity, error)
    Delete(ctx context.Context, entity *Entity, mod *handler.HandlerMod) error
}

Dependency Injection

Services are wired together through explicit dependency injection:

type GardenFetcher struct {
    Fetcher     *fetcher.Fetcher     // Base functionality
    GardenTable *tables.GardenTable // Entity mapping
}

type GardenHandler struct {
    Handler     *handler.Handler     // Base functionality
    GardenTable *tables.GardenTable // Entity mapping
    Spy         *spy.Spy            // Operation logging
    Time        time.Time           // Time utilities
}

Service Composition Patterns

Layered Service Usage

// Mae uses multiple services in coordination
func (this *CreateGardenMae) Execute(ctx context.Context, input *CreateGardenMaeInput) (*CreateGardenMaeOutput, error) {
    // 1. Form validation
    if err := this.GardenValidator.ValidateCreateForm(input.Form); err != nil {
        return nil, err
    }
    
    // 2. Transform form to entity
    garden := &mdl.Garden{}
    garden, changes := this.GardenMolder.ToEntity(input.Form, garden)
    
    // 3. Persist entity
    createdGarden, err := this.GardenHandler.Create(ctx, garden, nil)
    if err != nil {
        return nil, err
    }
    
    // 4. Load relationships if needed
    if input.IncludeRelationships {
        err := this.GardenHydrator.OneViaPreset(ctx, createdGarden, "show", nil)
        if err != nil {
            return nil, err
        }
    }
    
    return &CreateGardenMaeOutput{
        Garden: createdGarden,
    }, nil
}

Service Chaining

// Services can be chained for complex operations
func (this *GardenController) List(c *gin.Context) {
    // 1. Fetch entities
    gardens, pagination, err := this.GardenFetcher.FindPage(c, mod)
    if err != nil {
        c.JSON(500, gin.H{"error": err.Error()})
        return
    }
    
    // 2. Hydrate relationships
    err = this.GardenHydrator.ManyViaPreset(c, gardens, "list", nil)
    if err != nil {
        c.JSON(500, gin.H{"error": err.Error()})
        return
    }
    
    // 3. Serialize for response
    jsonData, err := this.GardenSerializer.SetToJson(gardens)
    if err != nil {
        c.JSON(500, gin.H{"error": err.Error()})
        return
    }
    
    c.Data(200, "application/json", jsonData)
}

Service Configuration Patterns

Modifier Objects

Services use modifier objects to configure operations:

// Fetcher modification
mod := gardenFetcher.Mod()
mod.ExactStringValueFilter("user_id", "123")
mod.DescOrder("created_at")
mod.Page = 1
mod.PerPage = 25

// Handler modification
handlerMod := &handler.HandlerMod{
    Tx: transaction,        // Use existing transaction
    Fields: []string{"name", "description"}, // Partial update
}

// Hydrator modification
hydratorMod := &hydrator.HydratorMod{}
hydratorMod.AddHydratingPath("plants")
hydratorMod.AddHydratingPath("garden_tasks", "assignee")

Service Presets

Services can define preset configurations:

// Hydrator presets
func (this *GardenHydrator) OneViaPreset(ctx context.Context, entity *mdl.Garden, preset string, mod *hydrator.HydratorMod) error {
    mod = this.Hydrator.ModDefaulting(mod)
    
    switch preset {
    case "show":
        mod.AddHydratingPath("plants")
        mod.AddHydratingPath("garden_tasks")
        mod.AddHydratingPath("plantations", "plants")
    case "list":
        mod.AddHydratingPath("user")
    case "api":
        mod.AddHydratingPath("plants", "plant_tasks")
    }
    
    return this.One(ctx, entity, mod)
}

Error Handling in Services

Service-Level Errors

// Services return structured errors
type ServiceError struct {
    Service   string
    Operation string
    Message   string
    Cause     error
}

func (this *GardenFetcher) FindOneById(ctx context.Context, id string, mod *fetcher.FetcherMod) (*mdl.Garden, bool, error) {
    if id == "" {
        return nil, false, &ServiceError{
            Service:   "GardenFetcher",
            Operation: "FindOneById",
            Message:   "ID parameter is required",
        }
    }
    
    // ... implementation
}

Error Propagation

// Services propagate errors with context
func (this *CreateGardenMae) Execute(ctx context.Context, input *CreateGardenMaeInput) (*CreateGardenMaeOutput, error) {
    garden, err := this.GardenHandler.Create(ctx, entity, nil)
    if err != nil {
        return nil, fmt.Errorf("failed to create garden in CreateGardenMae: %w", err)
    }
    
    return output, nil
}

Testing Service Layer

Service Unit Testing

func TestGardenHandler_Create(t *testing.T) {
    // Setup service with mock dependencies
    handler := &handlers.GardenHandler{
        Handler:     mockHandler,
        GardenTable: mockTable,
    }
    
    // Test creation
    garden := &mdl.Garden{Name: "Test Garden"}
    result, err := handler.Create(ctx, garden, nil)
    
    // Assertions
    require.NoError(t, err)
    assert.NotEmpty(t, result.Id)
    assert.Equal(t, "Test Garden", result.Name)
}

Service Integration Testing

func TestGardenServices_Integration(t *testing.T) {
    // Setup real database and services
    db := setupTestDatabase(t)
    services := setupGardenServices(t, db)
    
    // Test service interaction
    form := &forms.CreateGardenForm{Name: "Integration Test"}
    garden := &mdl.Garden{}
    garden, _ = services.GardenMolder.ToEntity(form, garden)
    
    created, err := services.GardenHandler.Create(ctx, garden, nil)
    require.NoError(t, err)
    
    found, exists, err := services.GardenFetcher.FindOneById(ctx, created.Id, nil)
    require.NoError(t, err)
    assert.True(t, exists)
    assert.Equal(t, created.Name, found.Name)
}

Performance Considerations

Service Optimization

  1. Batch Operations - Use batch methods when available
  2. Connection Pooling - Proper database connection management
  3. Query Optimization - Efficient query patterns in Fetchers
  4. Memory Management - Avoid loading large datasets unnecessarily
  5. Caching Strategy - Cache frequently accessed data

Monitoring and Observability

// Services integrate with monitoring
type GardenHandler struct {
    Handler *handler.Handler
    Spy     *spy.Spy  // Operation logging
    Metrics *metrics.Metrics // Performance metrics
}

func (this *GardenHandler) Create(ctx context.Context, entity *mdl.Garden, mod *handler.HandlerMod) (*mdl.Garden, error) {
    start := time.Now()
    defer func() {
        this.Metrics.RecordDuration("garden.handler.create", time.Since(start))
    }()
    
    this.Spy.LogOperation(ctx, "GardenHandler.Create", entity.Id)
    
    // ... implementation
}

LLM Service Development Notes

When working with the service layer: 1. Complete Coverage - Always implement full service suite for entities 2. Consistent Interfaces - Follow established patterns for each service type 3. Explicit Dependencies - Wire all dependencies through constructor injection 4. Error Handling - Provide structured errors with operation context 5. Testing Strategy - Test services both in isolation and integration 6. Performance Awareness - Consider query efficiency and resource usage 7. Monitoring Integration - Include logging and metrics in service operations