go-atlas

module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 20, 2026 License: MIT

README

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
}
Unified Response Format

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
Pagination
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

Directories

Path Synopsis
aether
log
artifact
id
Package main demonstrates a complete Atlas service.
Package main demonstrates a complete Atlas service.
pillar
ratelimit
Package ratelimit provides a Redis-backed distributed RateLimiter for the atlas framework, so multiple service replicas share a single quota.
Package ratelimit provides a Redis-backed distributed RateLimiter for the atlas framework, so multiple service replicas share a single quota.
serviceclient
Package serviceclient provides typed HTTP clients for inter-service communication.
Package serviceclient provides typed HTTP clients for inter-service communication.
sms
snowflake
Package snowflake is an atlas Pillar that assigns each instance a unique Snowflake worker ID (0..1023) — automatically via a Redis lease by default, or from a static config override.
Package snowflake is an atlas Pillar that assigns each instance a unique Snowflake worker ID (0..1023) — automatically via a Redis lease by default, or from a static config override.
telemetry
Package telemetry is the unified OpenTelemetry Pillar for Atlas.
Package telemetry is the unified OpenTelemetry Pillar for Atlas.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL