go-activerecord is a high-performance ORM generator for Go implementing the Active Record pattern with support for Octopus/Tarantool 1.5 and PostgreSQL.
Active Record pattern is a database access approach where each table is wrapped in a class and each row is represented as an object with methods for data manipulation.
// Declarative description
type FieldsUser struct {
Id int64 `ar:"primary_key"`
Name string `ar:"size:256;selector:SelectByName"`
}
// Generates type-safe code
user := user.New(ctx)
user.SetName("John")
user.Insert(ctx)
// Type-safe selectors for each index
users, _ := user.SelectByName(ctx, "John")Why it matters:
- β‘ Zero runtime overhead β no reflection, all checks at compile time
- π‘οΈ Type safety β errors caught by compiler, not in production
- π IDE autocomplete β full support for method autocompletion
- π Performance β speed comparable to hand-written SQL
user, _ := user.SelectById(ctx, 123)
// Increment executed atomically in DB, not in application
user.IncLoginCount(1) // UPDATE users SET login_count = login_count + 1
// Bitwise operations with named flags (no magic numbers!)
user.SetBitFlags(user.FlagsPremiumFlag) // UPDATE users SET flags = flags | 4
user.ClearBitFlags(user.FlagsActiveFlag) // UPDATE users SET flags = flags & ~1
user.Update(ctx)Benefits:
- π No race conditions β operations executed atomically in DB
- πΎ Traffic savings β no need for read-modify-write
- β‘ High performance β one operation instead of three
// Octopus/Tarantool 1.5
//ar:namespace:5
//ar:backend:octopus
// PostgreSQL
//ar:namespace:users
//ar:backend:postgresUniqueness:
- π Unified codebase for different databases
- π Smooth migration from Tarantool to PostgreSQL
- ποΈ Hybrid architecture β different data in different databases
- π¦ Legacy support β works with old Octopus/Tarantool 1.5
// β Attempting to select millions of records without limit
users, err := user.SelectByStatus(ctx, "active", nil) // COMPILATION ERROR!
// β
Mandatory limiter for non-unique indexes
limiter := activerecord.NewLimiter(1000)
users, err := user.SelectByStatus(ctx, "active", limiter)
// β Attempting to change primary key
user.SetId(456) // COMPILATION ERROR - method unavailable!Built-in protections:
- π‘οΈ Mandatory limiters β impossible to accidentally select entire database
- π Immutable PKs β primary keys protected from modification
- π― SQL injection protection β prepared statements out of the box
- π Limit warnings β data selection control
# Cluster configuration
cluster:
Timeout: 200ms
PoolSize: 10
shards:
1:
master: "10.0.1.1:3301,10.0.1.2:3301"
replica: "10.0.2.1:3301,10.0.2.2:3301"
2:
master: "10.0.3.1:3301"
replica: "10.0.4.1:3301"// Automatic shard and replica selection for reads
users, _ := user.SelectByIds(ctx, []int64{1, 1000, 2000})Capabilities:
- π Horizontal scaling β automatic sharding
- π Read replicas β automatic read load balancing
- π Failover β automatic switching on master failure
- βοΈ Runtime configuration β settings changes without restart
type FieldsConfig struct {
// JSON out of the box
Settings string `ar:"serializer:Json;size:2048"`
// Printf for formatting
Version string `ar:"serializer:Printf,%d.%d.%d;size:16"`
// Custom logic
Tags string `ar:"serializer:CustomTags;size:512"`
}
// Work with deserialized types in code
cfg.SetSettings(map[string]interface{}{
"theme": "dark",
"lang": "en",
})Built-in serializers:
- π¦ Json β encoding/json from standard library
- π¨ Printf β template formatting
- π§ Mapstructure β flexible deserialization
- β¨ Custom β your own serializers in 2 functions
// Built-in Prometheus integration
metrics := prometheus.NewMetrics()
activerecord.RegisterMetrics(metrics)
// Detailed logging
logger := zerolog.New(os.Stdout)
activerecord.RegisterLogger(&logger)Metrics out of the box:
- β±οΈ Timing β query execution time (select_db, update_db, etc)
- π Counters β operation counts (insert_success, update_success)
- β Errors β detailed error statistics
- π― Per-namespace β metrics for each table separately
| Feature | go-activerecord | GORM | sqlx | sqlc |
|---|---|---|---|---|
| Type safety | β Compile-time | β Manual | β Compile-time | |
| Reflection overhead | β None | β Yes | β None | β None |
| Code generation | β Yes | β No | β No | β Yes |
| Octopus/Tarantool | β Yes | β No | β No | β No |
| PostgreSQL | β Yes | β Yes | β Yes | β Yes |
| Atomic mutators | β Yes | β No | β No | β No |
| Sharding | β Built-in | β Manual | β Manual | |
| Error protection | β Compile-time | β None | β Compile-time | |
| Metrics | β Built-in | β None | β None |
Benchmark_Insert-8 50000 28543 ns/op 2048 B/op 12 allocs/op
Benchmark_Select-8 100000 12431 ns/op 896 B/op 8 allocs/op
Benchmark_Update-8 75000 19834 ns/op 1024 B/op 10 allocs/op
Benchmark_AtomicInc-8 100000 11234 ns/op 512 B/op 6 allocs/op
Why it's fast:
- π No reflection in runtime
- πΎ Connection pool reuse
- π¦ Efficient serialization
- β‘ Prepared statements cached
- π Less code β no need to write CRUD by hand
- π Fewer bugs β errors caught by compiler
- π Easy to learn β declarative approach is intuitive
- π IDE support β method autocompletion
- π Fast time-to-market β code generation instead of writing
- π° Resource savings β high performance
- π‘οΈ Reliability β built-in error protection
- π Scalability β sharding out of the box
- π Observability β metrics out of the box
- π§ Simple configuration β one config for everything
- π Zero-downtime β runtime configuration
- ποΈ Multi-backend β flexibility in database choice
# Install argen utility
git clone https://github.com/Educentr/go-activerecord
cd go-activerecord
make install
# Add dependency to project
go get github.com/Educentr/go-activerecord/v31. Create model declaration:
// model/repository/declaration/user.go
package repository
//ar:serverConf:mydb
//ar:namespace:users
//ar:backend:postgres
type FieldsUser struct {
Id int64 `ar:"primary_key"`
Email string `ar:"unique;size:256;selector:SelectByEmail"`
Name string `ar:"size:256"`
LoginCount uint32 `ar:"mutators:inc,dec"`
Flags uint32 `ar:""` // mutators added via FlagsUser
CreatedAt uint32 `ar:""`
}
type IndexesUser struct {
EmailCreated bool `ar:"fields:Email,CreatedAt;unique"`
}
// Named bit flags - generates constants FlagsActiveFlag, FlagsVerifiedFlag, etc.
type FlagsUser struct {
Flags string `ar:"flags:Active,Verified,Premium,_,Admin"`
}2. Generate code:
argen --path "model/repository" \
--declaration "declaration" \
--destination "generated"3. Use in code:
package main
import (
"context"
"github.com/Educentr/go-activerecord/v3/pkg/activerecord"
"yourapp/model/repository/generated/user"
)
func main() {
ctx := context.Background()
// Initialize config
activerecord.RegisterConfig(yourConfig)
// Create user
u := user.New(ctx)
u.SetEmail("[email protected]")
u.SetName("John Doe")
u.Insert(ctx)
// Search by email
found, err := user.SelectByEmail(ctx, "[email protected]")
if err != nil {
panic(err)
}
// Atomic increment
found.IncLoginCount(1)
// Set flags using named constants
found.SetBitFlags(user.FlagsVerifiedFlag)
found.Update(ctx)
fmt.Printf("User %s logged in %d times\n",
found.GetName(), found.GetLoginCount())
}go-activerecord is used in production in high-load projects:
- β Tested on billions of queries
- β Works with petabytes of data
- β Supports clusters of hundreds of servers
- β Used by Mail.ru and other major companies
We welcome contributions! See CONTRIBUTING.md for details.
The project is distributed under the MIT license. See LICENSE file for details.
Made with β€οΈ at Educentr