Tables Service Kind

Overview

Tables are entity-specific services responsible for database-to-domain model mapping and data transformation. They handle the conversion between database representations (with nullable SQL types) and domain entities, provide metadata about database schema, and manage field-level data transformations.

Architecture

Core Components

Entity-Specific Tables (src/srv/tables/) - Each domain model has its own dedicated table service (e.g., GardenTable, UserTable) - All tables follow the same interface pattern and method conventions - No base library - tables implement focused data mapping logic

Key Data Structures - Entity Structs: Domain model entities with Go native types - ESO Structs: Entity Storage Objects with SQL-compatible nullable types - Mapping Methods: Bidirectional conversion between ESOs and entities

Naming Convention

Tables follow a consistent naming pattern: - Table Services: {Entity}Table (e.g., GardenTable) - Storage Objects: {Entity}Eso (e.g., GardenEso) - ESO = Entity Storage Object (database-compatible representation)

Standard Methods

Every table service implements these standard methods:

Entity Conversion

EsoToEntity(eso) *Entity
  • Convert ESO (database representation) to domain entity
  • Handle NULL value conversion to Go zero values
  • Transform SQL types to native Go types

Database Scanning Support

EsoToPointers(eso) []interface{}
  • Generate pointers for SQL row scanning
  • Return slice of field pointers for rows.Scan()
  • Enable efficient database result processing

Entity-to-Values Conversion

EntityToValues(entity) []interface{}
  • Convert domain entity to database values
  • Transform native Go types to SQL-compatible values
  • Support database insertion and update operations

Metadata Methods

TableName() string               // Database table name
Columns() []string              // Database column names
Fields() []string               // Go struct field names
ColumnsWithoutId() []string     // Columns excluding ID field

Usage Examples

Database Result Processing

// In Fetcher services
eso := &tables.GardenEso{}
row := db.QueryRow(query, args...)
err := row.Scan(gardenTable.EsoToPointers(eso)...)

// Convert to domain entity
entity := gardenTable.EsoToEntity(eso)

Database Operations

// In Handler services
values := gardenTable.EntityToValues(entity)
columns := gardenTable.Columns()
tableName := gardenTable.TableName()

// Use for INSERT/UPDATE operations

Field Mapping

// Get metadata for query building
columns := gardenTable.Columns()       // ["id", "created_at", "updated_at", "name", "picture"]
fields := gardenTable.Fields()         // ["Id", "CreatedAt", "UpdatedAt", "Name", "Picture"]
table := gardenTable.TableName()       // "gardens"

ESO (Entity Storage Object) Pattern

ESOs represent database-compatible entity structures:

SQL-Compatible Types

type GardenEso struct {
    Id        string        // Direct mapping for required fields
    CreatedAt sql.NullTime  // Nullable database fields
    UpdatedAt sql.NullTime  // Handle NULL values properly
    Name      string        // Required business fields
    Picture   string        // Optional but non-null fields
}

NULL Handling

  • sql.NullTime: For optional timestamp fields
  • sql.NullString: For optional text fields
  • sql.NullInt64: For optional integer fields
  • sql.NullBool: For optional boolean fields

Entity Conversion Logic

ESO to Entity

func (this *GardenTable) EsoToEntity(eso *GardenEso) *mdl.Garden {
    entity := &mdl.Garden{}
    entity.Id = eso.Id
    entity.CreatedAt = eso.CreatedAt.Time    // Extract Time from sql.NullTime
    entity.UpdatedAt = eso.UpdatedAt.Time    // Handle null as zero value
    entity.Name = eso.Name
    entity.Picture = eso.Picture
    return entity
}

Entity to Values

func (this *GardenTable) EntityToValues(entity *mdl.Garden) []interface{} {
    var values []interface{}
    values = append(values,
        entity.Id,
        entity.CreatedAt,    // Go time.Time converts to SQL timestamp
        entity.UpdatedAt,
        entity.Name,
        entity.Picture,
    )
    return values
}

Database Scanning Support

Tables provide efficient scanning support for SQL queries:

Pointer Generation

func (this *GardenTable) EsoToPointers(eso *GardenEso) []interface{} {
    var values []interface{}
    values = append(values,
        &eso.Id,        // Pointer to string field
        &eso.CreatedAt, // Pointer to sql.NullTime
        &eso.UpdatedAt, // Pointer to sql.NullTime
        &eso.Name,      // Pointer to string field
        &eso.Picture,   // Pointer to string field
    )
    return values
}

Usage in Fetchers

// Efficient scanning with generated pointers
fields := gardenTable.EsoToPointers(eso)
err := rows.Scan(fields...)

Schema Metadata

Tables provide comprehensive database schema metadata:

Column Management

// Full column list
columns := gardenTable.Columns()
// ["id", "created_at", "updated_at", "name", "picture"]

// Columns without ID (for INSERT operations)
insertColumns := gardenTable.ColumnsWithoutId()
// ["created_at", "updated_at", "name", "picture"]

Field Mapping

// Go struct field names
fields := gardenTable.Fields()
// ["Id", "CreatedAt", "UpdatedAt", "Name", "Picture"]

Integration with Framework

With Fetchers

  • Provide ESO scanning support
  • Convert database results to entities
  • Supply schema metadata for query building

With Handlers

  • Convert entities to database values
  • Provide column lists for SQL operations
  • Supply table names for query construction

With Serializers

  • Field name mapping for JSON output
  • Entity value extraction for serialization

Type Safety

Tables maintain type safety throughout the conversion process: - Compile-time checking: All conversions use statically typed methods - NULL safety: Proper handling of nullable database fields - Type conversion: Safe conversion between SQL and Go types - Field ordering: Consistent field ordering across all operations

Performance Considerations

  • Direct field access: No reflection-based mapping for better performance
  • Efficient scanning: Pre-allocated pointer slices for database scanning
  • Memory efficiency: Reusable ESO structures
  • Minimal allocations: Optimized conversion methods