shredbx logo
shredbx shredbx shredbx shredbx Personal
  • Home
  • Lab
  • Portfolio
  • Experience
  • Services
  • Profile
  • Contact
AClaude
  • Home
  • Lab
  • Portfolio
  • Experience
  • Services
  • Profile
  • Contact
Andrei Solovev
Knowledge
Search knowledge... ⌘K
Knowledge · Guidelines · go

Type Safe Persistence

Enforce compile-time type safety for all database CRUD wrappers — no map[string]any, no string field names, no untyped values

Andrei Solovev

Metadata

go go recommended

Procedures

Showing 3 of 5

  1. 1 Define typed Field descriptors for every database column
    Every entity model MUST define a Fields struct where each member is a
    Field[T] carrying the column name and Go type. This enables compile-time
    checked filtering — Field[int64].GTE("string") won't compile.
    Field descriptors are defined ONCE per entity, next to the model struct.
    // Field[T] — typed column descriptor (compile-time safe)
    type Field[T any] struct{ Column string }
    
    func (f Field[T]) Eq(val T) Predicate   { return Predicate{f.Column, OpEq, val} }
    func (f Field[T]) GTE(val T) Predicate   { return Predicate{f.Column, OpGTE, val} }
    func (f Field[T]) LTE(val T) Predicate   { return Predicate{f.Column, OpLTE, val} }
    func (f Field[T]) In(vals []T) Predicate { /* ... */ }
    
    // Per-entity Fields struct — compile-time column registry
    var PropertyFields = struct {
        PriceAmount     Field[int64]
        TransactionType Field[string]
        IsPublished     Field[bool]
        City            Field[string]
    }{
        PriceAmount:     Field[int64]{Column: "price_amount"},
        TransactionType: Field[string]{Column: "transaction_type"},
        IsPublished:     Field[bool]{Column: "is_published"},
        City:            Field[string]{Column: "city"},
    }
    
    // COMPILES: Field[int64].GTE(int64)
    store.List(ctx, PropertyFields.PriceAmount.GTE(500000000))
    
    // WON'T COMPILE: Field[int64].GTE(string)
    store.List(ctx, PropertyFields.PriceAmount.GTE("wrong type"))
  2. 2 Use typed param structs for Insert and Update — never map[string]any
    Each entity defines dedicated Insert and Update param structs with concrete
    field types. The Model interface methods return these typed structs, not
    map[string]any. This ensures column-value type alignment at compile time.
    // Typed param structs — one per operation
    type PropertyInsert struct {
        Title           string
        TransactionType string
        PropertyType    string
        IsPublished     bool
        PriceAmount     int64
        PriceCurrency   string
        CoverImageID    *string
        CoverImageURL   *string
    }
    
    type PropertyUpdate struct {
        Title           *string  // nil = no change
        IsPublished     *bool
        PriceAmount     *int64
        PriceCurrency   *string
    }
    
    // Model interface — typed, not map[string]any
    type Model[I any, U any] interface {
        Table() string
        PrimaryKey() (column string, value any)
        InsertParams() I
        UpdateParams() U
        ScanRow(scanner RowScanner) error
    }
  3. 3 Use struct tag or explicit column mapping for row scanning — never positional
    Row scanning MUST map columns to struct fields by name, not by position.
    Use pgx RowToStructByName with `db` tags, or explicit named mapping in
    ScanRow. Positional scanning breaks silently when column order changes.
    // CORRECT: Named field mapping with db tags
    type Property struct {
        ID              string    `db:"id"`
        Title           string    `db:"title"`
        PriceAmount     int64     `db:"price_amount"`
        PriceCurrency   string    `db:"price_currency"`
        IsPublished     bool      `db:"is_published"`
    }
    
    // pgx named scanning — resilient to column reordering
    rows, _ := db.Query(ctx, "SELECT id, title, price_amount, ...")
    props, err := pgx.CollectRows(rows, pgx.RowToStructByName[Property])
    
    // WRONG: Positional scanning — breaks if columns reorder
    // rows.Scan(&p.ID, &p.Title, &p.PriceAmount)  // fragile

Tools

  • pkg/crud— Generic typed CRUD Store[M] with Field[T] predicates
  • pkg/types— Compound type contracts (Money, ImageRef) with typed expansion
  • pkg/database— pgx pool wrapper for complex typed queries via pgx.CollectRows
  • go vet / go build— Compile-time verification — Field[T] type mismatches caught here

References

  • external go-jet typed column expressions
  • external sqlc typed param structs
  • external incident.io Partial[T] pattern
  • external sq type-safe query builder
  • external Andrew Pillar generic Store[M]
  • external JPA @Embeddable + @AttributeOverride
  • external EF Core 8 ComplexType
  • external GORM embedded + embeddedPrefix
shredbx logo shredbx shredbx shredbx shredbx Andrei Solovev

Solution Architect & Lead Software Engineer

ExperiencePortfolioResearch & ExperimentsEducationCertificationSkills
GitHub ↗LinkedIn ↗Email ↗
AVAILABLE FOR NEW PROJECTS
// MY LATEST BEATS
Hobby & Interests

Lab

  • The Lab
  • Framework
  • Components
  • Packages
  • Games
  • Process (SDLC)
  • Knowledge
  • Blog

Andrei

  • Portfolio
  • Experience
  • Services
  • Profile
  • Contact
  • Lifestyle

Team

  • Team
  • Andrei
  • Claude

Legal

  • Privacy
  • Terms
  • Cookies
© 2026 shredbx.com. All rights reserved. — Andrei Solovev |