Files
summercms/docs/architecture/application-lifecycle.md
Jakub Zych 1f8f5e1b51 feat(11.1-03): add the Architecture and Plugins docs sections
- architecture: introduction, Go modules and workspaces, application
  lifecycle, request lifecycle
- plugins: registration, scheduling, extending, testing
- verified Examples for backpack services, towel request context,
  pact schedules and festival events
- TestDocsRequiredPages lists the eight new pages
2026-09-30 22:12:08 +02:00

76 lines
4.8 KiB
Markdown

---
title: Application lifecycle
description: "What happens when an application binary starts: configuration, the backpack container, plugin ordering, Register and Boot, and database-dependent boot work."
section: architecture
order: 30
---
# Application lifecycle
Every run of an application binary, whether it serves HTTP or runs a single console command, goes through the same start-up. `summer build` generates the `main.go` that performs it, so you never write it by hand.
## Start-up sequence
The generated `main` does the following, in order:
1. Loads configuration with `compass.Load` from the `config/` directory, applying the environment directory and `SUMMER_` variables.
2. Creates the application container with `backpack.New`.
3. Activates the plugins listed in `summer.yaml` with `party.Activate`.
4. Collects the console commands: the framework's runtime commands (from `lagoon.RuntimeCommands`, `conga.RuntimeCommands`, `surf.ServeCommand`, `surf.RouteListCommand` and `cabana.RuntimeCommands`), then the commands of every plugin that implements `pact.HasCommands`.
5. Publishes the command set as a `bonfire.Catalog`, so the scheduler can run commands in-process.
6. Runs the command named on the command line.
## Plugin ordering
`party.Activate` selects the plugins by ID and orders them so that every plugin comes after the plugins its `party.Plugin.Requires` lists. It fails before any plugin code runs when an ID is empty, duplicated or not compiled in, when a required plugin is missing, or when the requirements form a cycle.
## Register, then Boot
Activation runs in phases, and each phase finishes for every plugin before the next begins:
1. `backpack.App.SetPlugins` records the complete plugin set, so `backpack.App.HasPlugin` answers correctly from the first Register onwards.
2. The embedded defaults of every plugin that implements `pact.HasConfig` are merged into the configuration under the plugin ID.
3. `party.Plugin.Register` runs for every plugin. Publish services here; do not use other plugins' services yet.
4. The framework publishes the translator and the mailer, and registers each plugin's translations and mail templates.
5. `party.Plugin.Boot` runs for every plugin. Look up services, register event listeners and extend other plugins here.
This is the WinterCMS `register` and `boot` split: when any Boot runs, every plugin has already registered.
## The container
`backpack.App` is the application container that Register and Boot receive. It holds the configuration in `backpack.App.Config`, the event bus in `backpack.App.Events` and a typed service registry. Nothing in it is process-global, so two applications in one test do not share state.
Services are keyed by their Go type. A plugin publishes a value with `backpack.App.Publish` and another plugin reads it with `backpack.App.Lookup` and the same type argument. Publish under an interface type when consumers should not depend on your implementation:
```go src=modules/backpack/example_test.go#ExampleApp_Publish
app := backpack.New(&compass.Config{})
app.SetPlugins([]string{"acme.greeter", "acme.blog"})
// acme.greeter, in its Register step: publish under the interface type.
var greeter Greeter = englishGreeter{}
if err := app.Publish(greeter); err != nil {
fmt.Println(err)
return
}
// acme.blog, in its Boot step: look the service up by the same type.
if app.HasPlugin("acme.greeter") {
if found, ok := app.Lookup[Greeter](); ok {
fmt.Println(found.Greet("blog"))
}
}
// A second Publish under the same type is refused.
fmt.Println(app.Publish(greeter) != nil)
// Output:
// Hello, blog
// true
```
## Capability interfaces
Beyond the four `party.Plugin` methods, a plugin declares what it contributes by implementing interfaces from [pact](../../modules/pact/README.md). The framework package that owns a capability finds it with a type assertion: surf asks for `pact.HasRoutes` and `pact.HasMiddleware`, lagoon for `pact.HasMigrations`, cabana for `pact.HasAdminControllers`, conga for `pact.HasJobs` and `pact.HasSchedule`. A plugin that does not implement an interface simply does not take part in that capability.
## Database-dependent boot work
Boot runs before any command opens the database: `migrate` and `serve` open it after activation, and commands such as `key:generate` never open it. Code that needs the database handle during boot, such as registering GORM callbacks, therefore goes through `lagoon.OnDatabase`. It runs the function immediately when the database is already published, and otherwise queues it until `lagoon.Publish` makes the shared `*sql.DB` and `*gorm.DB` handles available. An error from a queued function is returned by `lagoon.Publish`, so the command that opened the database fails instead of running with a half-registered plugin. [Extending plugins](../plugins/extending.md) shows where GORM callbacks fit.