Atom
Type-segregated atomic value decomposition for Go.
The Problem
Serializing Go structs for storage or transmission typically involves either:
- Reflection-heavy marshaling (JSON, gob) - flexible but slow, with runtime type discovery
- Code generation (protobuf, msgpack) - fast but requires build steps and schema files
- 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 │
└─────────────────┘
- UseT registers a type and builds an execution plan
- Atomize() decomposes a struct into an Atom using the cached plan
- 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
| Category | Types |
|---|---|
| Primitives | string, int*, uint*, float*, bool, time.Time, []byte |
| Pointers | *string, *int64, *float64, *bool, *time.Time, *[]byte |
| Slices | []string, []int64, []float64, []bool, []time.Time, [][]byte |
| Named | type Status int, type UserID string, type IP []byte |
| Nested | Embedded 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
- Quickstart - Get up and running in 5 minutes
- Concepts - Understand atoms, tables, and specs
- API Reference - Complete API documentation