zoobzio January 5, 2025 Edit this page

Atom

Type-segregated atomic value decomposition for Go.

The Problem

Serializing Go structs for storage or transmission typically involves either:

  1. Reflection-heavy marshaling (JSON, gob) - flexible but slow, with runtime type discovery
  2. Code generation (protobuf, msgpack) - fast but requires build steps and schema files
  3. Manual serialization - fast and flexible but tedious and error-prone

None of these approaches make it easy to:

  • Store individual fields in type-appropriate backends (strings in Redis, numbers in time-series DBs)
  • Query or index specific fields without deserializing the entire object
  • Evolve schemas while maintaining type safety

The Solution

Atom decomposes structs into type-segregated maps, where each primitive type gets its own storage table:

type User struct {
    Name      string
    Age       int64
    Balance   float64
    Active    bool
    CreatedAt time.Time
}

// Decomposed into:
atom.Strings["Name"] = "alice"
atom.Ints["Age"] = 30
atom.Floats["Balance"] = 100.50
atom.Bools["Active"] = true
atom.Times["CreatedAt"] = time.Now()

This separation enables:

  • Type-native storage: Store strings in one system, numbers in another
  • Field-level access: Read or write individual fields without full deserialization
  • Schema introspection: Query which fields exist and their types at runtime

How It Works

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   Your Struct   │────▶│    Atomizer     │────▶│      Atom       │
│                 │     │                 │     │                 │
│  Name: "alice"  │     │  - Reflection   │     │  Strings: {...} │
│  Age: 30        │     │  - Type plans   │     │  Ints: {...}    │
│  Balance: 100.5 │     │  - Validation   │     │  Floats: {...}  │
└─────────────────┘     └─────────────────┘     └─────────────────┘
                                │
                                ▼
                        ┌─────────────────┐
                        │    Registry     │
                        │                 │
                        │  Cached plans   │
                        │  per type       │
                        └─────────────────┘
  1. UseT registers a type and builds an execution plan
  2. Atomize() decomposes a struct into an Atom using the cached plan
  3. Deatomize() reconstructs a struct from an Atom

Key Features

Type Safety

  • Compile-time generic type parameters
  • Runtime overflow detection for numeric conversions
  • Validation of supported field types at registration

Performance

  • One-time reflection cost per type (cached in registry)
  • Pre-allocated maps sized to exact field counts
  • Atomizable/Deatomizable interfaces enable code generation to bypass reflection

Flexibility

  • Supports all Go primitive types and their pointers
  • Nested structs and slices of structs
  • Named types (enums, type aliases)
  • Custom serialization via Atomizable/Deatomizable interfaces

Supported Types

CategoryTypes
Primitivesstring, int*, uint*, float*, bool, time.Time, []byte
Pointers*string, *int64, *float64, *bool, *time.Time, *[]byte
Slices[]string, []int64, []float64, []bool, []time.Time, [][]byte
Namedtype Status int, type UserID string, type IP []byte
NestedEmbedded structs, *Struct, []Struct

Quick Example

package main

import "github.com/zoobz-io/atom"

type User struct {
    ID    int64
    Name  string
    Email string
}

func main() {
    // Register the type (cached after first call)
    atomizer, err := atom.Use[User]()
    if err != nil {
        panic(err)
    }

    // Decompose
    user := &User{ID: 1, Name: "Alice", Email: "alice@example.com"}
    a := atomizer.Atomize(user)

    // Access individual fields by type
    fmt.Println(a.Ints["ID"])      // 1
    fmt.Println(a.Strings["Name"]) // Alice

    // Reconstruct
    restored, err := atomizer.Deatomize(a)
    if err != nil {
        panic(err)
    }
    fmt.Println(restored.Name) // Alice
}

Next Steps