Skip to content

Anclax is a framework for building serverless and reliable applications in speed of light with confidence

Notifications You must be signed in to change notification settings

cloudcarver/anclax

Repository files navigation

⚓ Anclax

English | 中文

social preview

Build serverless, reliable apps at lightspeed ⚡ — with confidence 🛡️.

Anclax is a definition‑first framework for small–medium apps (single PostgreSQL). Define APIs and tasks as schemas; generated code moves correctness to compile time.

Join our Discord server.

Recommended setup

Use the Anclax skill with the coding agent:

  1. Install Anclax CLI:
go install github.com/cloudcarver/anclax/cmd/anclax@latest
  1. Init your project:
anclax init myapp github.com/me/myapp
cd myapp
  1. Add Anclax skill to your coding agent
npx skills add cloudcarver/anclax

Highlights ✨

  • YAML-first, codegen-backed: Define HTTP and task schemas in YAML; Anclax generates strongly-typed interfaces so missing implementations fail at compile time, not in prod.
  • Async tasks you can trust: At-least-once delivery, automatic retries, and cron scheduling out of the box.
  • Transaction-safe flows: A WithTx pattern ensures hooks always run and side effects are consistent.
  • Typed database layer: Powered by sqlc for safe, fast queries.
  • Fast HTTP server: Built on Fiber for performance and ergonomics.
  • AuthN/Z built-in: Macaroons-based authentication and authorization.
  • Pluggable architecture: First-class plugin system for clean modularity.
  • Ergonomic DI: Wire-based dependency injection keeps code testable and explicit.

Why Anclax? (The problem it solves) 🤔

  • Glue-code fatigue: Many teams stitch HTTP, DB, tasks, DI, and auth by hand, leaving implicit contracts and runtime surprises. Anclax makes those contracts explicit and generated.
  • Background jobs are hard: Idempotency, retries, and delivery guarantees are non-trivial. Anclax ships a task engine with at-least-once semantics and cron.
  • Consistency across boundaries: Keep handlers, tasks, and hooks transactional using WithTx so invariants hold.
  • Confidence and testability: Every generated interface is mockable; behavior is easy to test.

Key advantages 🏆

  • Compile-time confidence: Schema → interfaces → concrete implementations you cannot forget to write.
  • Productivity: anclax init + anclax gen reduces boilerplate and wiring.
  • Extensibility: Clean plugin boundaries and event-driven architecture.
  • Predictability: Singletons for core services, DI for clarity, and well-defined lifecycles.

Architecture 🏗️

Anclax helps you build quickly while staying scalable and production‑ready.

  • Single PostgreSQL backbone: One PostgreSQL database powers both transactional business logic and the durable task queue, keeping state consistent and operations simple. For many products, a well‑provisioned instance (e.g., 32 vCPU) goes a very long way.
  • Stateless application nodes: HTTP servers are stateless and horizontally scalable; you can run multiple replicas without coordination concerns.
  • Task queue as integration fabric: Use async tasks to decouple modules. For example, when a payment completes, enqueue an OrderFinished task and do any factory‑module inserts in its handler—no factory logic inside the payment module.
  • Built‑in worker, flexible deployment: Anclax includes an async task worker. Run it in‑process, as separate long‑running workers, or disable it for serverless HTTP (e.g., AWS Lambda) while keeping workers on regular servers.
  • Monolith, not microservices: Anclax favors a pragmatic, scalable monolith and is not aimed at multi‑million QPS microservice fleets.

These choices maximize early velocity and give you a clear, reliable path to scale with confidence.

Hands-on: try it now 🧑‍💻

# 1) Scaffold into folder 'demo'
anclax init demo github.com/you/demo

# 2) Generate code (can be re-run anytime)
cd demo
anclax gen

# 3) Start the stack (DB + API + worker)
docker compose up

In another terminal:

curl http://localhost:2910/api/v1/counter
# Optional sign-in if your template includes auth
curl -X POST http://localhost:2910/api/v1/auth/sign-in -H "Content-Type: application/json" -d '{"name":"test","password":"test"}'

One‑minute tour 🧭

  1. Define an endpoint (OpenAPI YAML) 🧩
paths:
  /api/v1/counter:
    get:
      operationId: getCounter
  1. Define a task ⏱️
tasks:
  incrementCounter:
    description: Increment the counter value
    cron: "*/1 * * * *"
  1. Generate and implement 🛠️
anclax gen
func (h *Handler) GetCounter(c *fiber.Ctx) error {
  return c.JSON(apigen.Counter{Count: 0})
}

Showcase: unique features 🧰

OpenAPI-powered middleware (no DSL)

x-check-rules:
  OperationPermit:
    useContext: true
    parameters:
      - name: operationID
        schema:
          type: string
  ValidateOrgAccess:
    useContext: true
    parameters:
      - name: orgID
        schema:
          type: integer
          format: int32

paths:
  /orgs/{orgID}/projects/{projectID}:
    get:
      operationId: GetProject
      security:
        - BearerAuth:
            - x.ValidateOrgAccess(c, orgID, "viewer")
            - x.OperationPermit(c, operationID)

Security scheme (JWT example)

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: macaroon

Async tasks: at-least-once, retries, cron

  • Pain points before: hand-building apigen.Task payloads and attributes was repetitive and easy to get wrong.
  • Pain points before: retry/cron/unique-tag logic got duplicated and drifted across services.
  • Pain points before: enqueueing inside a DB transaction required custom glue code.
  • Pain points before: task params and handler signatures could fall out of sync.

Refactor solution: define tasks in api/tasks.yaml, run anclax gen, and use the generated taskgen.TaskRunner (RunX / RunXWithTx) with taskcore overrides when needed.

# api/tasks.yaml
tasks:
  - name: SendWelcomeEmail
    description: Send welcome email to new users
    parameters:
      type: object
      required: [userId, templateId]
      properties:
        userId:
          type: integer
          format: int32
        templateId:
          type: string
    retryPolicy:
      interval: 5m
      maxAttempts: 3
    cron: "0 * * * *"

Before (manual task record):

params := &taskgen.SendWelcomeEmailParameters{UserId: user.ID, TemplateId: "welcome"}
payload, err := params.Marshal()
if err != nil {
  return err
}

task := &apigen.Task{
  Spec: apigen.TaskSpec{Type: taskgen.SendWelcomeEmail, Payload: payload},
  Attributes: apigen.TaskAttributes{
    RetryPolicy: &apigen.TaskRetryPolicy{Interval: "5m", MaxAttempts: 3},
  },
  Status: apigen.Pending,
}

taskID, err := taskStore.PushTask(ctx, task)

After (generated runner + transaction-safe enqueue):

err := model.RunTransactionWithTx(ctx, func(tx core.Tx, txm model.ModelInterface) error {
  _, err := taskrunner.RunSendWelcomeEmailWithTx(ctx, tx, &taskgen.SendWelcomeEmailParameters{
    UserId: user.ID,
    TemplateId: "welcome",
  }, taskcore.WithUniqueTag("welcome:"+strconv.Itoa(int(user.ID))))
  return err
})

Transactions: compose everything with WithTx

func (s *Service) CreateUserWithTx(ctx context.Context, tx pgx.Tx, username, password string) (int32, error) {
  txm := s.model.SpawnWithTx(tx)
  userID, err := txm.CreateUser(ctx, username, password)
  if err != nil { return 0, err }
  if err := s.hooks.OnUserCreated(ctx, tx, userID); err != nil { return 0, err }
  _, err = s.taskRunner.RunSendWelcomeEmailWithTx(ctx, tx, &taskgen.SendWelcomeEmailParameters{ UserId: userID })
  return userID, err
}

Dependency injection with Wire

func NewGreeter(m model.ModelInterface) (*Greeter, error) { return &Greeter{Model: m}, nil }
func InitApp() (*app.App, error) {
  wire.Build(model.NewModel, NewGreeter /* ...other providers... */)
  return nil, nil
}

Typed SQL with sqlc

-- name: GetCounter :one
SELECT value FROM counter LIMIT 1;

-- name: IncrementCounter :exec
UPDATE counter SET value = value + 1;

Advanced: Custom initialization 🧩

You can run custom logic before the app starts by providing an Init function:

// Runs before the application starts
func Init(anclaxApp *anclax_app.Application, taskrunner taskgen.TaskRunner, myapp anclax_app.Plugin) (*app.App, error) {
    if err := anclaxApp.Plug(myapp); err != nil {
        return nil, err
    }

    if _, err := anclaxApp.GetService().CreateNewUser(context.Background(), "test", "test"); err != nil {
        return nil, err
    }
    if _, err := taskrunner.RunAutoIncrementCounter(context.Background(), &taskgen.AutoIncrementCounterParameters{
        Amount: 1,
    }, taskcore.WithUniqueTag("auto-increment-counter")); err != nil {
        return nil, err
    }

    return &app.App{ AnclaxApp: anclaxApp }, nil
}

To customize how the Anclax application is constructed, override InitAnclaxApplication:

func InitAnclaxApplication(cfg *config.Config) (*anclax_app.Application, error) {
    anclaxApp, err := anclax_wire.InitializeApplication(&cfg.Anclax, anclax_config.DefaultLibConfig())
    if err != nil {
        return nil, err
    }
    return anclaxApp, nil
}

Need more dependencies inside Init? Add them as parameters (e.g., model.ModelInterface) and run anclax gen.

Documentation 📚

Examples 🧪

  • examples/simple — minimal end-to-end sample with HTTP, tasks, DI, and DB.

About

Anclax is a framework for building serverless and reliable applications in speed of light with confidence

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •