Atlas
中文文档
Atlas — the Titan who bears the heavens on his shoulders. He carries the sky; this framework carries your services.
A refined Go framework for building production-grade backend services. Built on Gin, designed for teams who value clarity over ceremony.
Atlas provides a cohesive set of building blocks — authentication, storage, caching, telemetry, inter-service communication, and more — wired together through a mythology-inspired four-domain architecture: Aether (built-in essentials), Pillar (pluggable infrastructure), and Artifact (standalone utilities), with sensible defaults and zero boilerplate.
Quick Start
package main
import (
"github.com/gin-gonic/gin"
"github.com/shiliu-ai/go-atlas/aether/response"
"github.com/shiliu-ai/go-atlas/atlas"
)
func main() {
a := atlas.New("my-service")
a.Route(func(r *gin.RouterGroup) {
r.GET("/health", func(c *gin.Context) {
response.OK(c, gin.H{"status": "ok"})
})
})
a.MustRun()
}
That's it. You get structured logging, request IDs, panic recovery, CORS, i18n, and graceful shutdown — out of the box.
Install
go get github.com/shiliu-ai/go-atlas
Requires Go 1.25+.
Architecture
Atlas is organized into four domains. The names come from Greek mythology, but each maps directly to a familiar concept:
| Domain |
Name |
Myth |
In conventional terms |
What goes here |
| Core |
Atlas |
The Titan who holds the sky |
The framework itself |
Config, server, lifecycle, middleware engine |
| Built-in |
Aether |
The divine air — everywhere, invisible |
Core libraries (like net/http in stdlib) |
errors, i18n, log, response — always available, no setup needed |
| Extension |
Pillar |
The columns that hold up the sky |
Plugins / Modules |
auth, database, cache, storage — opt-in, with lifecycle |
| Toolkit |
Artifact |
Divine tools forged by gods |
Utility packages (like a utils/ folder) |
crypto, ID generation, pagination — pure functions, no framework dependency |
Rule of thumb: if you need atlas.New() to use it, it's a Pillar. If you can use it in any Go project, it's an Artifact. If it's always there without importing anything extra, it's Aether.
atlas/ The Titan — framework core
*.go config, server, lifecycle, middleware, Pillar interface
aether/ The divine air — built-in essentials (always available)
├── errors Structured error codes → HTTP status mapping
├── i18n Internationalization with Accept-Language detection
├── log Structured logging (slog-based)
└── response Unified JSON API responses
pillar/ The columns — pluggable infrastructure (opt-in via Pillar())
├── auth JWT authentication
├── cache Redis cache + distributed locks
├── database GORM (MySQL/PostgreSQL, multiple named connections)
├── httpclient Production-ready HTTP client
├── oauth OAuth2 providers
├── serviceclient Typed inter-service RPC
├── sms SMS sending (Tencent Cloud)
├── storage Object storage (S3/COS/OSS/TOS)
└── telemetry OpenTelemetry tracing + metrics (unified Resource, OTLP + Prometheus)
artifact/ Divine tools — standalone utilities (zero framework dependency)
├── crypto Password hashing, AES-GCM encryption
├── id UUID, NanoID, ShortID, NumericID, Snowflake
├── jsonutil JSON helpers
├── pagination Page/size binding and response
└── validate Request binding with validation
Pillar Pattern
Pillars are opt-in infrastructure modules — think of them as plugins with a managed lifecycle. Atlas initializes them at startup and shuts them down gracefully on exit.
Every Pillar follows the same two-step pattern: register → retrieve.
import (
"github.com/shiliu-ai/go-atlas/atlas"
"github.com/shiliu-ai/go-atlas/pillar/auth" // ← Pillar package
"github.com/shiliu-ai/go-atlas/pillar/database"
"github.com/shiliu-ai/go-atlas/pillar/cache"
)
// Step 1 — Register: tell Atlas which Pillars you need.
// Each xxx.Pillar() returns an atlas.Option that registers the module.
a := atlas.New("my-service",
auth.Pillar(), // registers JWT auth
database.Pillar(), // registers GORM database manager
cache.Pillar(), // registers Redis cache
)
// Step 2 — Retrieve: get the initialized instance via xxx.Of(a).
// This is type-safe — you get the concrete type, not an interface.
jwt := auth.Of(a) // *auth.JWT
dbm := database.Of(a) // *database.Manager
redis := cache.Of(a) // *cache.RedisCache
Under the hood, all Pillars implement the atlas.Pillar interface:
type Pillar interface {
Name() string
Init(core *Core) error // called at startup
Stop(ctx context.Context) error // called at shutdown (reverse order)
}
Pillars can optionally implement Starter (background goroutines), HealthChecker (health endpoint), or MiddlewareProvider (auto-injected middleware).
Configuration
Atlas uses Viper under the hood. Drop a config.yaml alongside your binary:
server:
port: 8080
name: "my-service"
mode: "release" # debug | release | test
read_timeout: 30s
write_timeout: 30s
shutdown_timeout: 10s
log:
level: "info" # debug | info | warn | error
format: "text" # text (default) | json
i18n:
default: "en" # default language tag, e.g. "en", "zh-Hans"
auth:
secret: "change-me"
issuer: "my-service"
access_expire: 2h
refresh_expire: 168h
databases:
default:
driver: "mysql" # mysql | postgres
dsn: "user:pass@tcp(127.0.0.1:3306)/mydb?charset=utf8mb4&parseTime=True"
max_open_conns: 50
max_idle_conns: 10
max_lifetime: 1h
log_level: "info"
# readonly:
# driver: "mysql"
# dsn: "user:pass@tcp(127.0.0.1:3307)/mydb?charset=utf8mb4&parseTime=True"
redis:
addr: "127.0.0.1:6379"
storages:
default:
driver: "s3" # s3 | cos | oss | tos
s3:
endpoint: "https://s3.amazonaws.com"
region: "us-east-1"
bucket: "my-bucket"
access_key_id: ""
secret_access_key: ""
# backup:
# driver: "cos"
# cos:
# bucket_url: "https://<bucket>-<appid>.cos.<region>.myqcloud.com"
# SMS (Tencent Cloud)
sms:
default:
driver: "tencentcloud"
tencent:
secret_id: "${SMS_SECRET_ID}"
secret_key: "${SMS_SECRET_KEY}"
app_id: "1400000000"
sign: "YourAppName"
region: "ap-guangzhou"
telemetry:
enabled: true
resource:
environment: "production"
otlp:
enabled: true
endpoint: "localhost:4318"
protocol: "http"
insecure: true
prometheus:
enabled: true
path: "/metrics"
traces:
enabled: true
sample_rate: 1.0
metrics:
enabled: true
runtime: true
http: true
cardinality_limit: 2000
exemplars: true
httpclient:
timeout: 5s
max_retries: 2
retry_wait: 500ms
services:
user-service:
base_url: "http://user-service:8080/user-service"
timeout: 5s # override global httpclient timeout
max_retries: 3 # override global httpclient retries
middleware:
cors:
allow_origins: ["*"]
allow_methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]
allow_headers: ["Origin", "Content-Type", "Authorization", "X-Request-ID"]
max_age: 86400
rate_limit:
rate: 100
window: 1m
All sections are optional. Pillars read their own config section during Init() — misconfiguration triggers a fail-fast panic at startup.
Environment variables are also supported with the APP_ prefix (configurable via WithEnvPrefix). Nested keys use underscores: APP_SERVER_PORT=9090.
Features
Authentication
JWT-based auth with access/refresh token pairs. HS256/384/512 signing.
a := atlas.New("my-service", auth.Pillar())
jwt := auth.Of(a)
// Generate token pair
pair, err := jwt.GeneratePair(userID, map[string]any{"role": "admin"})
// Protect routes
a.Route(func(r *gin.RouterGroup) {
authorized := r.Group("/api", jwt.Middleware())
// Extract claims
authorized.GET("/me", func(c *gin.Context) {
claims := auth.ClaimsFromContext(c.Request.Context())
response.OK(c, gin.H{"user_id": claims.UserID})
})
})
Database
GORM-based ORM with multiple named connections, lazy initialization, connection pooling, and MySQL/PostgreSQL support.
a := atlas.New("my-service", database.Pillar())
dbm := database.Of(a)
// Default connection
db, err := dbm.Default()
// Named connection (e.g. read-only replica)
db, err := dbm.Get("readonly")
Cache
Redis client with unified cache interface and distributed locking.
a := atlas.New("my-service", cache.Pillar())
redis := cache.Of(a)
redis.Set(ctx, "key", "value", 5*time.Minute)
val, err := redis.Get(ctx, "key")
// Distributed lock
l := redis.NewLock("my-lock", 10*time.Second)
acquired, err := l.Acquire(ctx)
defer l.Release(ctx)
ok, err := l.Extend(ctx, 30*time.Second) // extend TTL
Object Storage
One interface, four cloud providers. Switch backends by changing a config line. Supports multiple named storage instances.
| Driver |
Provider |
s3 |
AWS S3, MinIO, S3-compatible |
cos |
Tencent Cloud COS |
oss |
Alibaba Cloud OSS |
tos |
Volcengine TOS |
a := atlas.New("my-service", storage.Pillar())
stm := storage.Of(a)
// Default storage
store, err := stm.Get("default")
err = store.Put(ctx, "path/to/file.png", reader, size, "image/png")
url, err := store.SignURL(ctx, "path/to/file.png", 15*time.Minute)
// Named storage
store, err := stm.Get("backup")
SMS
Unified SMS sending with multi-provider support (Tencent Cloud); named instances for multi-tenant/OEM scenarios.
a := atlas.New("my-service", sms.Pillar())
smsMgr := sms.Of(a)
s, err := smsMgr.Default()
err = s.Send(ctx, &sms.SendRequest{
Phone: "+8613800138000",
TemplateID: "123456",
Params: []string{"1234", "5"},
})
Inter-Service Communication
Typed HTTP clients for calling other atlas-based services. Automatically unwraps the standard R{code, message, data} response envelope, forwards request headers (Authorization, X-Request-ID, X-Trace-ID), and supports per-service timeout/retry overrides.
a := atlas.New("my-service",
httpclient.Pillar(),
serviceclient.Pillar(),
)
svcm := serviceclient.Of(a)
userSvc := svcm.MustGet("user-service")
// Typed call — response.data is unmarshalled into the target
var user User
err := serviceclient.Get(ctx, userSvc, "/v1/users/123", &user)
// With query parameters
var users []User
err := serviceclient.Get(ctx, userSvc, "/v1/users", &users,
serviceclient.WithQuery(url.Values{"page": {"1"}, "size": {"20"}}),
)
// POST with body
var created User
err := serviceclient.Post(ctx, userSvc, "/v1/users", createReq, &created)
HTTP Client
Production-ready HTTP client with retries, exponential backoff, and trace propagation.
a := atlas.New("my-service", httpclient.Pillar())
hc := httpclient.Of(a)
resp, err := hc.Get(ctx, "https://api.example.com/data")
body := resp.String()
resp, err := hc.PostJSON(ctx, url, payload)
resp, err := hc.PutJSON(ctx, url, payload)
resp, err := hc.Delete(ctx, url)
ID Generation
Four strategies for different needs (standalone utilities, no Pillar needed):
id.UUID() // "550e8400-e29b-41d4-a716-446655440000"
id.NanoID() // "V1StGXR8_Z5jdHi6B-myT"
id.ShortID() // "0h7a8sK2x9pL3mN1"
id.NumericID() // 1711929600001230001
sf, _ := id.NewSnowflake(1)
sf.MustGenerate() // 182439823049723904
Structured Errors
Code-based errors that map cleanly to HTTP status codes. Supports i18n message keys.
errors.New(errors.CodeNotFound, "user not found")
errors.NewT(errors.CodeNotFound, "error.user_not_found") // i18n key
errors.Wrap(errors.CodeInternal, "database query failed", err)
// Predefined sentinel errors
errors.ErrNotFound // 404
errors.ErrUnauthorized // 401
errors.ErrBadRequest // 400
// Fluent API
errors.ErrNotFound.WithMessage("user not found")
errors.ErrNotFound.WithMsgKey("error.user_not_found")
// In handlers
response.Fail(c, errors.CodeBadRequest, "invalid email format")
response.FailT(c, errors.CodeBadRequest, "error.invalid_email") // i18n
response.Err(c, err) // auto-detect *errors.Error
Request Validation
Bind and validate in one step with human-readable error messages:
type CreateUserReq struct {
Email string `json:"email" binding:"required,email"`
Name string `json:"name" binding:"required,min=2,max=50"`
}
var req CreateUserReq
if !validate.BindJSON(c, &req) {
return // error response already sent
}
Consistent JSON responses across your entire API:
response.OK(c, data)
// {"code": 0, "message": "ok", "data": {...}, "trace_id": "..."}
response.Fail(c, errors.CodeNotFound, "user not found")
// {"code": 404, "message": "user not found", "trace_id": "..."}
response.Err(c, err) // derive response from *errors.Error
response.AbortErr(c, err) // same as Err but aborts middleware chain
authorized.GET("/users", func(c *gin.Context) {
pg := pagination.FromContext(c) // auto-bind ?page=1&size=20
users, total := fetchUsers(pg.Offset(), pg.Size)
response.OK(c, pagination.NewResponse(users, total, pg))
})
Internationalization (i18n)
Built-in i18n support with per-request locale detection via Accept-Language header.
// Register custom translations
bundle := a.I18nBundle()
bundle.Register(language.English, map[string]string{
"error.user_not_found": "User not found",
})
bundle.Register(language.SimplifiedChinese, map[string]string{
"error.user_not_found": "用户不存在",
})
// Use i18n in responses
response.FailT(c, errors.CodeNotFound, "error.user_not_found")
Cryptography
Standalone utilities, no Pillar needed:
// Password hashing (bcrypt)
hash, _ := crypto.HashPassword("secret")
ok := crypto.CheckPassword(hash, "secret")
// AES-GCM encryption
cipher, _ := crypto.NewAES(key)
encrypted, _ := cipher.EncryptString("sensitive data")
decrypted, _ := cipher.DecryptString(encrypted)
OAuth2
a := atlas.New("my-service", oauth.Pillar())
oauthMgr := oauth.Of(a)
github := oauthMgr.MustGet("github")
url := github.AuthCodeURL("state-token")
Custom Configuration
Use a.Unmarshal() to read custom config sections from the same config file:
type BusinessConfig struct {
MaxItems int `mapstructure:"max_items"`
Region string `mapstructure:"region"`
}
var bizCfg BusinessConfig
if err := a.Unmarshal("business", &bizCfg); err != nil {
panic(err)
}
Middleware
Atlas registers these middleware by default:
| Middleware |
Description |
| Recovery |
Catches panics, logs stack traces, returns 500 |
| Request ID |
Generates/propagates X-Request-ID header |
| I18n |
Detects locale from Accept-Language header |
| Logging |
Structured request logs with latency, status, path |
| CORS |
Configurable cross-origin resource sharing |
| Rate Limit |
Sliding window rate limiter (if configured) |
Pillars that implement MiddlewareProvider (e.g. telemetry, serviceclient) automatically inject their middleware between core defaults and user middleware.
Logging is context-aware — trace IDs and request IDs flow through automatically.
Disable all defaults with WithoutDefaultMiddleware(), or add custom middleware with WithMiddleware(...).
Observability
Register the telemetry Pillar for unified OpenTelemetry tracing and metrics:
a := atlas.New("my-service", telemetry.Pillar())
t := telemetry.Of(a)
tracer := t.Tracer("billing")
meter := t.Meter("billing")
One Pillar, one shared Resource, one OTLP connection. Traces and metrics are emitted in lockstep; histogram buckets carry the active trace_id as exemplars so you can jump from a P99 spike straight to the slow request.
telemetry:
enabled: true
resource:
environment: "production"
otlp:
enabled: true
endpoint: "localhost:4318"
protocol: "http"
insecure: true
prometheus:
enabled: true # exposes /metrics on the service base path
path: "/metrics"
traces:
enabled: true
sample_rate: 1.0
metrics:
enabled: true
runtime: true # Go runtime: GC, goroutines, heap
http: true # auto HTTP RED (semconv-compliant)
cardinality_limit: 2000 # hard cap on attribute combinations
exemplars: true # metric → trace correlation
HTTP RED metrics follow OTel semconv (http.server.request.duration, http.server.active_requests) with http.route set from gin's matched template so labels stay bounded. Traces propagate across HTTP client calls and inter-service communication; every response includes a trace_id for end-to-end debugging.
For extension points (custom Views, extra Resource attributes, shared Prometheus registry, opt-out of OTel globals) see pillar/telemetry/README.md.
Example
See example/main.go for a complete working example with auth, pagination, inter-service calls, proxying, and ID generation.
License
MIT