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:
2026-06-29 17:58:06 -07:00
parent a07d9d06ed
commit 58e5e33e18
3 changed files with 96 additions and 63 deletions
+64 -63
View File
@@ -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