docs: add doc.go and expand README for V1
Document lifecycle concepts, companion packages, and API reference. Add gitignore for coverage artifacts. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# Test coverage
|
||||
coverage.out
|
||||
coverage.html
|
||||
|
||||
# Go build artifacts
|
||||
*.test
|
||||
@@ -1,12 +1,14 @@
|
||||
# app
|
||||
|
||||
app is a simple and lightweight app lifecycle management library.
|
||||
app orchestrates modular application setup and teardown for Go services.
|
||||
|
||||
## Example
|
||||
## Install
|
||||
|
||||
Keep your `main.go` as simple as possible, all it's responsible for is creating the lifecycle, defining a default logger, and orchestrating setup and teardown.
|
||||
```bash
|
||||
go get gitea.auvem.com/go-toolkit/app
|
||||
```
|
||||
|
||||
**main.go**
|
||||
## Quick start
|
||||
|
||||
```go
|
||||
package main
|
||||
@@ -16,75 +18,74 @@ import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"gitea.auvem.com/go-toolkit/applog"
|
||||
"gitea.auvem.com/go-toolkit/app"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a new lifecycle, defaulting to no printed logs
|
||||
lifecycle := app.NewLifecycle().WithLogger(slog.New(slog.DiscardHandler))
|
||||
lifecycle := app.NewLifecycle(
|
||||
applog.AppLogOpts{ConsoleOutput: os.Stderr}.Module(),
|
||||
)
|
||||
defer func() {
|
||||
if err := lifecycle.Teardown(); err != nil {
|
||||
fmt.Println("Error during shutdown", err)
|
||||
fmt.Println("shutdown error:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Encodes the lifecycle into a context to be used downstream
|
||||
ctx := lifecycle.Context(context.Background())
|
||||
|
||||
// Off to you, call your entrypoint here.
|
||||
Hello(ctx)
|
||||
}
|
||||
```
|
||||
|
||||
Now, let's define a basic logging module and handle loading that module.
|
||||
|
||||
**logger.go**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"gitea.auvem.com/go-toolkit/app"
|
||||
)
|
||||
|
||||
func ModuleLog(setDefault bool) *app.Module {
|
||||
return app.NewModule("logger", app.ModuleOpts{
|
||||
Setup: func(m *app.Module) error {
|
||||
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{})
|
||||
if setDefault {
|
||||
slog.SetDefault(handler)
|
||||
}
|
||||
|
||||
m.Lifecycle().WithLogger(handler)
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Logging can, however, be somewhat of a complex operation in itself, so I'd highly recommend checking out my [applog](https://gitea.auvem.com/go-toolkit/applog) project, which features tight integration with this app.
|
||||
|
||||
Finally, lets put our new lifecycle (and logger) to work!
|
||||
|
||||
**hello.go**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"gitea.auvem.com/go-toolkit/app"
|
||||
)
|
||||
|
||||
func Hello(ctx context.Context) {
|
||||
app := app.LifecycleFromContext(ctx)
|
||||
if app == nil {
|
||||
panic("hello must run within an app lifecycle")
|
||||
if err := lifecycle.Setup(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
lifecycle.Require(ModuleLog(true))
|
||||
|
||||
app.Logger().Info("Hello world!", "foo", "bar")
|
||||
ctx := lifecycle.Context(context.Background())
|
||||
_ = ctx
|
||||
lifecycle.Logger().Info("ready")
|
||||
}
|
||||
```
|
||||
|
||||
## Concepts
|
||||
|
||||
- **Module** — a named subsystem with optional `Setup`, `Teardown`, and `Depends`
|
||||
- **Lifecycle** — holds modules and runs setup/teardown in dependency order
|
||||
- **Setup** — initializes all modules registered via `NewLifecycle`
|
||||
- **Require** — adds and initializes modules on demand (typical for CLI subcommands via [appcli](https://gitea.auvem.com/go-toolkit/appcli))
|
||||
- **Autoload** — when enabled (default), `Depends` names are set up automatically before the dependent module
|
||||
|
||||
## Dependencies
|
||||
|
||||
List dependency module **names** in `ModuleOpts.Depends`. Dependent modules must be registered in the same lifecycle (via `NewLifecycle` or an earlier `Require`). Circular dependencies return an error.
|
||||
|
||||
## Teardown
|
||||
|
||||
Always defer `lifecycle.Teardown()`. Teardown runs in **reverse setup order** so dependents shut down before their dependencies.
|
||||
|
||||
## Context
|
||||
|
||||
Use `lifecycle.Context(ctx)` so downstream code and `appcli` commands can call `app.LifecycleFromContext(ctx)`.
|
||||
|
||||
## Companion packages
|
||||
|
||||
| Package | Role |
|
||||
|---------|------|
|
||||
| [applog](https://gitea.auvem.com/go-toolkit/applog) | slog + tint logging module |
|
||||
| [appcli](https://gitea.auvem.com/go-toolkit/appcli) | urfave/cli v3 integration |
|
||||
| [dbx](https://gitea.auvem.com/go-toolkit/dbx) | database module |
|
||||
| [migrate](https://gitea.auvem.com/go-toolkit/migrate) | goose migrations module |
|
||||
|
||||
## API reference
|
||||
|
||||
| Symbol | Description |
|
||||
|--------|-------------|
|
||||
| `NewLifecycle(modules...)` | Create lifecycle with optional initial modules |
|
||||
| `Lifecycle.Setup()` | Set up all registered modules |
|
||||
| `Lifecycle.Teardown()` | Tear down in reverse setup order |
|
||||
| `Lifecycle.Require(modules...)` | Set up modules on demand |
|
||||
| `Lifecycle.RequireWithOpts(opts, modules...)` | Require with custom logger or unique-name enforcement |
|
||||
| `Lifecycle.GetModule(name)` | Look up a registered module |
|
||||
| `NewModule(name, opts)` | Define a module |
|
||||
| `LifecycleFromContext(ctx)` | Retrieve lifecycle from context |
|
||||
|
||||
## Error handling
|
||||
|
||||
- Partial setup failure rolls back already-initialized modules
|
||||
- `Teardown` joins errors from all modules but attempts every teardown
|
||||
- A second `Setup` or `Teardown` on the same lifecycle returns an error
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Package app orchestrates modular application setup and teardown.
|
||||
//
|
||||
// Register subsystems as [Module] values, attach them to a [Lifecycle], and call
|
||||
// [Lifecycle.Setup] or [Lifecycle.Require] to initialize resources in dependency
|
||||
// order. Always call [Lifecycle.Teardown] (typically via defer) to release
|
||||
// resources in reverse setup order.
|
||||
//
|
||||
// Module authors (dbx, applog, migrate, etc.) and main/cmd packages both import
|
||||
// app. Use [Lifecycle.Context] to propagate the lifecycle through context.Context
|
||||
// for CLI commands via [gitea.auvem.com/go-toolkit/appcli].
|
||||
//
|
||||
// # Concepts
|
||||
//
|
||||
// - [Module] — named unit with optional Setup, Teardown, and Depends
|
||||
// - [Lifecycle] — registry and orchestrator for modules
|
||||
// - Setup order — dependencies first; autoload resolves Depends when enabled
|
||||
// - Require — lazy/conditional setup (common in CLI subcommands)
|
||||
//
|
||||
// # API overview
|
||||
//
|
||||
// Lifecycle — [NewLifecycle], [Lifecycle.Setup], [Lifecycle.Teardown],
|
||||
// [Lifecycle.Require], [Lifecycle.RequireWithOpts], [Lifecycle.GetModule],
|
||||
// [Lifecycle.Context], [LifecycleFromContext]
|
||||
//
|
||||
// Module — [NewModule], [Module.Lifecycle], [Module.Logger], [Module.Loaded]
|
||||
package app
|
||||
Reference in New Issue
Block a user