Core Concepts
Understanding the building blocks of atom.
Atoms
An Atom is the decomposed representation of a struct. It contains type-segregated maps where each field is stored under its name in the appropriate map for its type.
type Atom struct {
Spec Spec // Type metadata
Strings map[string]string // String fields
Ints map[string]int64 // Integer fields (all sizes)
Uints map[string]uint64 // Unsigned integer fields
Floats map[string]float64 // Float fields
Bools map[string]bool // Boolean fields
Times map[string]time.Time // Time fields
Bytes map[string][]byte // Byte slice fields
// ... pointers, slices, nested
}
Why Type Segregation?
Type segregation enables:
- Type-native storage: Store integers in Redis sorted sets, strings in search indices
- Efficient serialization: Each map can use optimal encoding for its type
- Partial access: Read only the fields you need without deserializing everything
Tables
A Table identifies which map in an Atom stores a particular field type.
type Table string
const (
TableStrings Table = "strings"
TableInts Table = "ints"
TableUints Table = "uints"
TableFloats Table = "floats"
TableBools Table = "bools"
TableTimes Table = "times"
TableBytes Table = "bytes"
// ... plus pointers and slices
)
Table Categories
| Category | Tables | Go Types |
|---|---|---|
| Scalars | strings, ints, uints, floats, bools, times, bytes | Primitive types |
| Pointers | string_ptrs, int_ptrs, etc. | *T where T is scalar |
| Slices | string_slices, int_slices, etc. | []T where T is scalar |
Querying Tables
atomizer, _ := atom.Use[User]()
// Get all fields
fields := atomizer.Fields()
// [{Name: "ID", Table: "ints"}, {Name: "Name", Table: "strings"}, ...]
// Get fields in a specific table
stringFields := atomizer.FieldsIn(atom.TableStrings)
// ["Name", "Email"]
// Get table for a field
table, ok := atomizer.TableFor("Age")
// table = "ints", ok = true
Specs
A Spec contains metadata about a type, including its name and package path. It is an alias for sentinel.Metadata:
type Spec = sentinel.Metadata
Key fields:
spec.TypeName // e.g., "User"
spec.PackageName // e.g., "github.com/example/app"
Specs are automatically populated from the sentinel library and can be accessed:
atomizer, _ := atom.Use[User]()
spec := atomizer.Spec()
fmt.Println(spec.TypeName) // "User"
fmt.Println(spec.PackageName) // "github.com/example/app"
Fields
A Field describes a single struct field and its storage location.
type Field struct {
Name string // Field name (e.g., "Age")
Table Table // Storage table (e.g., "ints")
}
Atomizers
An AtomizerT is the typed interface for converting between structs and atoms.
type Atomizer[T any] struct {
// internal
}
func (a *Atomizer[T]) Atomize(obj *T) *Atom
func (a *Atomizer[T]) Deatomize(atom *Atom) (*T, error)
func (a *Atomizer[T]) NewAtom() *Atom
func (a *Atomizer[T]) Spec() Spec
func (a *Atomizer[T]) Fields() []Field
func (a *Atomizer[T]) FieldsIn(table Table) []string
func (a *Atomizer[T]) TableFor(field string) (Table, bool)
Atomizers are obtained via the Use[T]() function and are cached in a global registry.
The Registry
The registry maintains a cache of atomizers indexed by type. When you call Use[T]():
- Check if an atomizer exists for type T
- If yes, return the cached instance
- If no, build an execution plan via reflection and cache it
// First call: builds atomizer (~microseconds)
atomizer1, _ := atom.Use[User]()
// Subsequent calls: returns cached (~nanoseconds)
atomizer2, _ := atom.Use[User]()
// atomizer1 and atomizer2 share the same internal state
Type Width Conversion
Atom normalizes integer and float types to their widest representation:
| Go Type | Atom Storage |
|---|---|
int8, int16, int32, int, int64 | int64 |
uint8, uint16, uint32, uint, uint64 | uint64 |
float32, float64 | float64 |
Overflow detection occurs during deatomization:
type Small struct {
Value int8 // Range: -128 to 127
}
atom := &Atom{Ints: map[string]int64{"Value": 200}}
_, err := atomizer.Deatomize(atom)
// err: "value 200 overflows int8 (range -128 to 127)"
Named Types
Named types (type aliases) are fully supported:
type UserID string
type Status int
const (
StatusActive Status = iota
StatusInactive
)
type User struct {
ID UserID
Status Status
}
// UserID stored in Strings table
// Status stored in Ints table
Next Steps
- Architecture - How atom works internally
- Basic Usage Guide - Common usage patterns