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