Documentation
¶
Overview ¶
Package appx builds a cobra-backed application context: build metadata, env-driven configuration, startup hooks, and shutdown.
Typical flow:
- NewAppContext(Main, options...) with WithMeta, optional WithRoot / WithPreInit / WithPreRun / WithServe / WithClose.
- AppCtx.Conf loads config from the environment (and optional config/local.yml), writes defaults under config/, runs WithPreInit, then initializes fields that implement confx types Init hooks.
- AppCtx.Execute dispatches cobra subcommands (`run`, `version`, or commands from AppCtx.AddCommand).
- AppCtx.Close on shutdown closes Conf components and WithClose hooks.
See example/appx for an end-to-end binary.
Meta and context ¶
Meta holds Name / Feature / Version / CommitID / CommitAt / BuildAt / Runtime. NewAppContext starts from DefaultMeta; WithMeta replaces it. AppCtx.Conf injects Meta via WithAppMeta so nested Initializers (e.g. confotel) can read it with AppMetaFrom / MustAppMeta.
app := appx.NewAppContext(
Main,
appx.WithMeta(appx.Meta{Name: "demo", Version: "v1.0.0"}),
appx.WithRoot("."),
)
app.Conf(ctx, &cfg)
Configuration ¶
AppCtx.Conf accepts one or more config pointers. Each named type becomes an envx group (e.g. DEMO__OTEL): merge local.yml → decode env → write config/default.yml and config/.env → WithPreInit → types.InitByContext on fields. Anonymous structs are allowed only when a single configuration is passed.
Lifecycle hooks and shutdown ¶
Duty boundary:
- WithPreInit: make components able to Init (metadata, hooks)
- Init (via AppCtx.Conf): resources ready, no business traffic
- WithContext (via Conf inject): capabilities into ctx
- WithPreRun: process-level prep with a complete ctx
- WithServe: long-lived services (preferred for daemon apps)
- Main (constructor arg): process owner after Serve starts
In AppCtx.Conf, after writing defaults: AppOption.PreInit → Init → inject. On `run`: AppOption.PreRun → AppOption.Serve (async) → Main:
- WithPreInit: before Init in Conf (metadata, hooks, …)
- WithPreRun: ordered process prep (global config, inject context, …)
- WithServe: long-lived services (HTTP server, cron jobs, …)
- Main: decides wait vs exit (signal / Serves done / oneshot); must AppCtx.Close. Framework does not infer Main from Serves emptiness.
- WithClose: extra close hooks; Conf components close automatically in AppCtx.Close and do not need registration
Prefer serve-first: put long-lived work in WithServe; use AppCtx.AddCommand for oneshot CLI tools. Main still owns when to exit.
CLI ¶
AppCtx.Execute runs the root cobra command from main. NewAppContext registers `run` (via AppCtx.AddCommand) and `version`.
Runtime ¶
GetRuntime reads RuntimeEnvKey (RUNTIME_ENV): PROD / STAGING / DEV (default PROD). Stored on Meta.Runtime.
Example (Init_failed) ¶
_ = os.Setenv("APP__SomeKey", "")
root := "./testdata2"
app := NewAppContext(
_main,
WithMeta(Meta{Name: "app"}),
WithRoot(root),
)
defer os.RemoveAll(root)
defer func() {
fmt.Println(recover())
}()
app.Conf(context.Background(), &struct {
SomeKey MustInitFailed
}{})
Output: failed to init [group:APP] [field:SomeKey]: must fail
Index ¶
- Constants
- Variables
- func BatchRun(runners ...func())
- func BatchRunSync(runners ...func())
- type AppCtx
- func (app *AppCtx) AddCommand(name, short string, runE func(args []string) error)
- func (app *AppCtx) Close(ctx context.Context) error
- func (app *AppCtx) Conf(ctx context.Context, configurations ...any) context.Context
- func (app *AppCtx) Execute() error
- func (app *AppCtx) Name() string
- func (app *AppCtx) Root() string
- func (app *AppCtx) Version() string
- type AppOption
- type Meta
- type Option
- type Runtime
Examples ¶
Constants ¶
const RuntimeEnvKey = "RUNTIME_ENV"
RuntimeEnvKey is the environment variable read by GetRuntime.
Variables ¶
var ( // AppMetaFrom returns [Meta] from ctx if present. // [AppCtx.Conf] injects Meta before types.InitByContext so Initializers // (for example confotel) can resolve service name and version. AppMetaFrom = contextx.From[tCtxMeta, Meta] // MustAppMeta returns [Meta] from ctx or panics when missing. MustAppMeta = contextx.Must[tCtxMeta, Meta] // WithAppMeta stores [Meta] in ctx. WithAppMeta = contextx.With[tCtxMeta, Meta] // CarrierAppMeta builds a contextx.Carrier for [Meta]. CarrierAppMeta = contextx.Carry[tCtxMeta, Meta] )
var DefaultMeta = Meta{ Name: "name", Feature: "branch", Version: "version", CommitID: "commit", CommitAt: time.Now().Format(metaTimeLayout), BuildAt: time.Now().Format(metaTimeLayout), Runtime: GetRuntime(), }
DefaultMeta is the baseline Meta used when WithMeta is omitted. Runtime is taken from GetRuntime at package init.
Functions ¶
func BatchRun ¶
func BatchRun(runners ...func())
BatchRun executes runners concurrently and waits for all to finish.
func BatchRunSync ¶
func BatchRunSync(runners ...func())
BatchRunSync executes runners sequentially in order.
Types ¶
type AppCtx ¶
type AppCtx struct {
// contains filtered or unexported fields
}
AppCtx is the application handle: cobra CLI, build meta, and env config groups.
func NewAppContext ¶
NewAppContext builds an AppCtx with main as the `run` entry, a hidden root cobra command, and a `version` subcommand. Apply options before AppCtx.Conf.
On `run`, the framework runs PreRun → Serve → main. main decides process lifetime (wait for signal / Serves, or oneshot exit) and should call Close.
Example ¶
root := "./testdata1"
app := NewAppContext(
_main,
WithRoot(root),
WithMeta(Meta{
Name: "app",
Feature: "main",
Version: "v0.0.1",
CommitID: "efbecda",
CommitAt: "20060102150405",
BuildAt: "20060102150505",
Runtime: RUNTIME_DEV,
}),
WithServe(
func() {
time.Sleep(time.Second * 1)
fmt.Println("batch runner 1")
},
func() {
time.Sleep(time.Second * 2)
fmt.Println("batch runner 2")
},
),
WithPreRun(
func() {
time.Sleep(time.Second * 1)
fmt.Println("pre runner 1")
},
func() {
time.Sleep(time.Second * 2)
fmt.Println("pre runner 2")
},
),
)
must.NoError(os.MkdirAll(filepath.Join(app.Root(), "config"), os.ModePerm))
must.NoError(os.WriteFile(filepath.Join(app.Root(), "config/local.yml"), []byte(`
APP__CONFIG1__Endpoint_Address: "postgres://hostname:5432/base"
APP__CONFIG1__WorkerID: "100"
APP__CONFIG2__ClientEndpoint_Address: http://localhost:80/demo
APP__CONFIG2__ServerPort: "8080"
`), os.ModePerm))
defer os.RemoveAll(root)
config1 := &Config1{}
config2 := &Config2{}
app.Conf(context.Background(), config1, config2)
cmd := app.cmd
buf := bytes.NewBuffer(nil)
cmd.SetOut(buf)
cmd.SetErr(buf)
{
fmt.Println("exec `app version`")
buf.Reset()
cmd.SetArgs([]string{"version"})
must.NoError(cmd.Execute())
fmt.Println(buf.String())
}
{
fmt.Println("exec `app run`")
buf.Reset()
cmd.SetArgs([]string{"run"})
must.NoError(cmd.Execute())
fmt.Println(buf.String())
}
Output: exec `app version` app:main@v0.0.1#efbecda(DEV)[commit=20060102150405|build=20060102150505] exec `app run` app:main@v0.0.1#efbecda(DEV)[commit=20060102150405|build=20060102150505] name: app feature: main version: v0.0.1 commit: efbecda commit_at: 20060102150405 build_at: 20060102150505 runtime: DEV APP__CONFIG1__Endpoint_Address=postgres://hostname:5432/base APP__CONFIG1__Endpoint_Auth_DecryptKey= APP__CONFIG1__Endpoint_Auth_Password=-------- APP__CONFIG1__Endpoint_Auth_Username= APP__CONFIG1__Endpoint_Cert_CA= APP__CONFIG1__Endpoint_Cert_Crt= APP__CONFIG1__Endpoint_Cert_Key= APP__CONFIG1__WorkerID=100 APP__CONFIG2__ClientEndpoint_Address=http://localhost:80/demo APP__CONFIG2__ClientEndpoint_Auth_DecryptKey= APP__CONFIG2__ClientEndpoint_Auth_Password=-------- APP__CONFIG2__ClientEndpoint_Auth_Username= APP__CONFIG2__ClientEndpoint_Cert_CA= APP__CONFIG2__ClientEndpoint_Cert_Crt= APP__CONFIG2__ClientEndpoint_Cert_Key= APP__CONFIG2__ServerPort=8080 pre runner 1 pre runner 2 batch runner 1 batch runner 2 main entry
func (*AppCtx) AddCommand ¶ added in v0.4.9
AddCommand registers a cobra subcommand under the app root.
name is the command Use string, short is the Short help text, and runE is invoked with the remaining CLI args when the command is selected. NewAppContext uses this to register `run`.
func (*AppCtx) Close ¶ added in v0.4.9
Close shuts down the application.
It first closes components registered through AppCtx.Conf via types.CloseByContext (no WithClose needed for those), then runs any callbacks registered with WithClose. Errors are joined and returned.
func (*AppCtx) Conf ¶
Conf loads one or more config pointers from the environment, writes default config files under AppCtx.Root/config, runs WithPreInit, then initializes fields that support types.InitByContext. Meta is injected into ctx via WithAppMeta before init.
Each named config type becomes an envx group (e.g. APP__OTEL). Anonymous structs are allowed only when a single configuration is passed.
func (*AppCtx) Execute ¶ added in v0.4.9
Execute runs the root cobra command (typically from main).
Dispatches to registered subcommands such as `run` / `version`, or any command added via AppCtx.AddCommand.
func (*AppCtx) Root ¶ added in v0.5.2
Root returns the path set by WithRoot, or the working directory used when constructing the app.
func (*AppCtx) Version ¶
Version returns the formatted build identity string from Meta.String.
type AppOption ¶
type AppOption struct {
*Meta
// PreInits run sequentially before component Init in [AppCtx.Conf]: prepare
// metadata or hooks so Init can succeed, without opening business traffic.
PreInits []func()
// PreRunners run sequentially before Serve: ordered process-level prep
// with a complete ctx, such as global config or context injection.
// Registered via [WithPreRun].
PreRunners []func()
// Serves run in parallel after PreRun: long-lived services such as
// HTTP servers or scheduled jobs. Registered via [WithServe].
Serves []func()
// CloseFns are extra close callbacks for [AppCtx.Close], registered via
// [WithClose]. Components from Conf are closed automatically and need not
// be listed here.
CloseFns []func() error
}
AppOption holds build Meta and startup hooks for an AppCtx.
func (*AppOption) PreInit ¶ added in v0.5.2
func (o *AppOption) PreInit()
PreInit runs WithPreInit callbacks sequentially before component Init.
func (*AppOption) PreRun ¶
func (o *AppOption) PreRun()
PreRun runs WithPreRun callbacks sequentially. Invoked by `run` before AppOption.Serve.
func (*AppOption) Serve ¶ added in v0.5.2
func (o *AppOption) Serve()
Serve starts WithServe callbacks in parallel (non-blocking). Invoked by `run` after AppOption.PreRun.
type Meta ¶
type Meta struct {
Name string `json:"name"`
Feature string `json:"feature"`
Version string `json:"version"`
CommitID string `json:"commit"`
CommitAt string `json:"commit_at"`
BuildAt string `json:"build_at"`
Runtime Runtime `json:"runtime"`
}
Meta is the build and runtime identity of an application.
Build-time fields (typically via -ldflags): Name, Feature, Version, CommitID, CommitAt, BuildAt. CommitID may carry a "-dirty" suffix when the working tree was dirty at build time. Runtime is filled from [RUNTIME_ENV] at process start and is not a build input.
func (*Meta) Print ¶
func (m *Meta) Print()
Print writes a colored multi-line Meta summary to stdout.
func (*Meta) String ¶
String formats Meta as:
name:feature@version#commit(runtime)[commit=commitAt|build=buildAt]
func (*Meta) VersionString ¶ added in v0.4.9
VersionString joins Name, Version, CommitID, and lowercase Runtime with "-".
type Option ¶
type Option func(*AppCtx)
Option configures an AppCtx at construction time.
func WithClose ¶ added in v0.5.2
WithClose registers extra close callbacks invoked by AppCtx.Close.
Closable components loaded via AppCtx.Conf do not need registration; they are closed automatically when AppCtx.Close runs.
func WithMeta ¶ added in v0.5.2
WithMeta sets the application Meta, replacing DefaultMeta.
func WithPreInit ¶ added in v0.5.2
func WithPreInit(fns ...func()) Option
WithPreInit appends ordered callbacks run before component Init in AppCtx.Conf.
Use to prepare the process so components can Init—for example registering metadata or hooks—without opening business traffic.
func WithPreRun ¶ added in v0.5.2
func WithPreRun(runners ...func()) Option
WithPreRun appends ordered callbacks run before WithServe on `run`.
Use for sequential process-level prep with a complete ctx—such as loading global config or injecting a global context—before long-lived services start.
func WithRoot ¶ added in v0.5.2
WithRoot sets the application root used to resolve config/local.yml, config/default.yml, and config/.env. When empty, NewAppContext keeps the process working directory.
type Runtime ¶
type Runtime string
Runtime is the deployment environment label stored on Meta.
func GetRuntime ¶
func GetRuntime() Runtime
GetRuntime returns the Runtime from RuntimeEnvKey, or RUNTIME_PROD when unset or unrecognized.