application
The application package provides the central orchestrator for managing the lifecycle of your application, including startup tasks, services, databases, and health checks.
Core Components:
Application: Central orchestrator that manages startup tasks, services, databases, and health checksRunner: Interface that services and startup tasks must implement to be executed by the applicationRunnerFunc: Function type that implementsRunnerfor simple inline tasksStartupTaskConfig: Configuration for startup tasks with name and abort-on-error behaviorDomain: Interface for self-contained modules that bundle repository and other componentsHealthchecker: Interface for services that can report their health statusHealthCheckHandler: HTTP handler for exposing application health as JSONApplicationHealth: Tracks overall application health and individual service statusesServiceHealth: Health status for a single service including start time and errors
Full package docs at pkg.go.dev
Step-by-step guide
Section titled “Step-by-step guide”-
Create a new
Applicationinstanceapp := application.New() -
Define a service that implements the
Runnerinterfacetype MyService struct{}func (s *MyService) Run(ctx context.Context) error {ticker := time.NewTicker(1 * time.Second)defer ticker.Stop()for {select {case <-ticker.C:log.InfoContext(ctx, "service tick")case <-ctx.Done():log.InfoContext(ctx, "service shutting down")return nil}}}The
Runmethod should block until the context is canceled or an error occurs. -
Register startup tasks that run before services start
app.OnStartFunc(func(ctx context.Context) error {log.InfoContext(ctx, "initialization complete")return nil}, application.StartupTaskConfig{Name: "init",AbortOnError: true,})Setting
AbortOnErrortotruestops the application if this task fails. UseOnStartforRunnerimplementations orOnStartFuncfor inline functions. -
Register your service with the application
app.RegisterService("my-service", &MyService{}) -
Run the application
ctx := context.Background()if err := app.Run(ctx); err != nil {log.ErrorContext(ctx, "app finished with error", "error", err)}The
Runmethod parses CLI arguments fromos.Args:Terminal window # Run database migrations./myapp migrate# Start the application (services and startup tasks)./myapp run# Show usage./myapp --help -
Run migrations first, then start your app
Terminal window ./myapp migrate./myapp runYou will see output like this:
time=2025-11-11T22:01:26.630+03:00 level=INFO msg="starting application" startupTasks=1time=2025-11-11T22:01:26.630+03:00 level=INFO msg="running task" task=init index=0time=2025-11-11T22:01:26.630+03:00 level=INFO msg="initialization complete"time=2025-11-11T22:01:26.631+03:00 level=INFO msg="starting service" serviceName=my-servicetime=2025-11-11T22:01:27.631+03:00 level=INFO msg="service tick"time=2025-11-11T22:01:28.631+03:00 level=INFO msg="service tick"Press Ctrl+C to gracefully shutdown the application.
CLI commands
Section titled “CLI commands”The Run method reads os.Args and executes the appropriate command:
| Command | Description |
|---|---|
run | Start the application (startup tasks + services) |
migrate | Run database migrations and exit |
--help, -h | Show usage information |
If no command is provided, usage information is printed.
Execution order
Section titled “Execution order”When you run ./myapp run, the following happens in order:
- Startup tasks - Tasks run sequentially in registration order
- Services - All services start concurrently in separate goroutines
- Wait - Application waits for context cancellation (Ctrl+C)
- Shutdown - Services receive context cancellation for graceful shutdown
When you run ./myapp migrate:
- Database migrations - All registered databases run their migrations
- Exit - Application exits after migrations complete
Register methods
Section titled “Register methods”RegisterService
Section titled “RegisterService”Registers a named service that runs concurrently when the application starts.
app.RegisterService("api", httpServer)app.RegisterService("queue-processor", processor)If the service implements Healthchecker, its health status is automatically tracked.
RegisterDatabase
Section titled “RegisterDatabase”Registers a database connection. Migrations are run when you execute the migrate command.
db, _ := database.New("postgres://user:pass@localhost:5432/mydb?sslmode=disable")app.RegisterDatabase("main", db)RegisterRepository
Section titled “RegisterRepository”Registers a repository with a database. The repository must have a Migrations() method.
app.RegisterRepository("main", "users", userRepo)RegisterDomain
Section titled “RegisterDomain”Registers a domain module. If a database name is provided, the domain’s repository is registered automatically.
app.RegisterDomain("auth", "main", authDomain)Health checks
Section titled “Health checks”Services implementing Healthchecker have their health tracked automatically:
type Healthchecker interface { Healthcheck(context.Context) any}Expose health via HTTP using HealthCheckHandler:
api.Handle("/health", application.NewHealthCheckHandler(app))The response includes application start time and per-service status:
{ "startedAt": "2025-01-01T12:00:00Z", "services": { "api": { "status": "STARTED", "startedAt": "2025-01-01T12:00:00Z" } }}Error handling
Section titled “Error handling”The application returns specific error types:
ErrUnknownCommand- Returned when an unknown CLI command is providedErrStartupTaskFailed- Returned when a startup task withAbortOnError: truefailsErrDatabaseMigrationFailed- Returned when database migration fails (frommigratecommand)
Both error types support unwrapping to get the underlying error:
if err := app.Run(ctx); err != nil { var migrationErr *application.ErrDatabaseMigrationFailed if errors.As(err, &migrationErr) { log.ErrorContext(ctx, "migration failed", "cause", migrationErr.Unwrap()) }}Complete example
Section titled “Complete example”package main
import ( "context" "fmt" "time"
"github.com/platforma-dev/platforma/application" "github.com/platforma-dev/platforma/log")
type Clock struct{}
func (r *Clock) Run(ctx context.Context) error { ticker := time.NewTicker(1 * time.Second) defer ticker.Stop()
for { select { case <-ticker.C: log.InfoContext(ctx, "tick") case <-ctx.Done(): log.InfoContext(ctx, "finished") return fmt.Errorf("context error: %w", ctx.Err()) } }}
func main() { ctx := context.Background() app := application.New()
app.RegisterService("clock", &Clock{})
if err := app.Run(ctx); err != nil { log.ErrorContext(ctx, "app finished with error", "error", err) }}