// modules combines caching, outbound calls, middleware and background jobs. package main import ( "context" "encoding/json" "errors" "flag" "log" "os" "os/signal" "sync/atomic" "syscall" "time" omni "git.yuntuo.hk/k2safe/go-omni" "git.yuntuo.hk/k2safe/go-omni/auth" "git.yuntuo.hk/k2safe/go-omni/cache" "git.yuntuo.hk/k2safe/go-omni/httpc" "git.yuntuo.hk/k2safe/go-omni/logx" "git.yuntuo.hk/k2safe/go-omni/middleware" "git.yuntuo.hk/k2safe/go-omni/task" ) func newApp(settings omni.Config, token, upstream string) (application *omni.App, err error) { application = omni.New(omni.WithConfig(settings)) defer func(app *omni.App) { if err != nil { _ = app.Close() } }(application) store, err := cache.NewMemory(cache.MemoryConfig{MaxEntries: 128, MaxBytes: 1 << 20}) if err != nil { return nil, err } application.OnShutdown(store.Close) loader, err := cache.NewLoader(store, cache.LoaderConfig{MaxLoads: 8, Timeout: time.Second}) if err != nil { return application, err } client, err := httpc.New(httpc.Config{Timeout: time.Second, MaxRetries: 1, MaxConnsPerHost: 8}) if err != nil { return application, err } application.OnShutdown(client.Close) jobs, err := task.New(task.Config{Workers: 2, QueueSize: 16, TaskTimeout: 2 * time.Second}) if err != nil { return application, err } // Reverse cleanup order: drain jobs before closing their dependencies. application.OnShutdown(jobs.Close) if err = jobs.Every("reportStats", time.Minute, func(ctx context.Context) error { logx.InfoContext(ctx, "jobs.stats", "stats", jobs.Stats()) return nil }); err != nil { return application, err } limiter, err := middleware.NewRateLimiter(middleware.RateLimitConfig{Rate: 20, Burst: 40, MaxKeys: 128, Key: func(c *omni.Context) string { p, _ := auth.FromContext(c); return p.UserID }}) if err != nil { return application, err } idempotency, err := middleware.NewIdempotency(middleware.IdempotencyConfig{Scope: func(c *omni.Context) (string, error) { p, _ := auth.FromContext(c); return p.UserID, nil }}) if err != nil { return application, err } api := application.Group("/api", middleware.Timeout(2*time.Second), auth.RequireAuth(auth.StaticAuthenticator{token: {UserID: "demo-user"}}), limiter.Middleware()) api.GET("/catalog", func(c *omni.Context) error { value, err := loader.GetOrLoad(c.Context(), "publicCatalog:v1", 30*time.Second, func(ctx context.Context) ([]byte, error) { data := map[string]any{"name": "demo catalog"} if upstream != "" { if err := client.JSON(ctx, "GET", upstream, nil, &data); err != nil { return nil, err } } return json.Marshal(data) }) if err != nil { return err } return c.OK(json.RawMessage(value)) }) var nextID atomic.Uint64 api.POST("/jobs", func(c *omni.Context) error { var input struct { Name string `json:"name" binding:"required,max=100"` } if err := c.BindJSON(&input); err != nil { return err } id := nextID.Add(1) // This job deliberately outlives the HTTP request. Runner still applies // its own deadline and shutdown cancellation; log correlation survives. _, err := jobs.Submit(context.WithoutCancel(c.Context()), "demoJob", func(ctx context.Context) error { logx.InfoContext(ctx, "job.finished", "jobId", id, "name", input.Name) return nil }) if errors.Is(err, task.ErrQueueFull) { return omni.NewError(503, "QUEUE_FULL", "job queue is full") } if err != nil { return err } return c.Accepted(map[string]uint64{"jobId": id}) }, idempotency.Middleware()) api.GET("/jobs", func(c *omni.Context) error { return c.OK(jobs.Stats()) }) return application, nil } func run(ctx context.Context) error { settings, err := omni.LoadConfig(*configPath) if err != nil { return err } token := os.Getenv("GO_OMNI_DEV_TOKEN") if token == "" { token = "dev-token" } application, err := newApp(settings, token, os.Getenv("GO_OMNI_DEMO_UPSTREAM")) if err != nil { return err } return application.RunContext(ctx) } var configPath = flag.String("config", "examples/modules/config.yaml", "configuration file") func main() { flag.Parse() ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() if err := run(ctx); err != nil { log.Fatal(err) } }