Files
summercms/modules/backpack/README.md
Jakub Zych 602e0412f3 docs: add a Role in the framework section to every module README
Each module README now says where the module sits in the dependency
graph, why it is its own module, and lists use cases for application
developers.
2026-10-11 00:58:17 +02:00

101 lines
5.1 KiB
Markdown

# backpack
Per-instance application container that holds the configuration, a typed service registry, the event bus and the set of activated plugins.
`import "git.golem15.com/golem15/summercms/modules/backpack"`
**Name:** The one bag you pack for the beach; it carries everything the application needs: configuration, services, events and plugins.
## Overview
`backpack` plays the role of the Laravel service container that WinterCMS plugins reach through `App::make` and singleton bindings, without any process-global state: every `backpack.App` is independent, so tests and multiple instances in one process do not interfere. The generated `main` of an application loads configuration with [compass](../compass/README.md), creates the container with `backpack.New`, and hands it to [party](../party/README.md), which passes it to every plugin's Register and Boot. `backpack` deliberately does not import `party`, which keeps the dependency graph acyclic.
## Role in the framework
The root of the framework. It depends only on `compass` (configuration) and `festival` (events), and almost every other module (`lagoon`, `conga`, `cabana`, `surf`, `party`, `phrasebook`, `postcard`, `lighthouse`, `flare`, `beachcomber`) receives the application through it. It is the Go counterpart of Laravel's service container: one value handed to every plugin, with typed lookups instead of string keys. It stays its own module, separate from `party`, so the plugin registry can depend on it without an import cycle.
Use cases:
- Reach shared services from a plugin (the database connection, the blob bucket, the translator) without globals.
- Publish a plugin's own service once at Register time so other plugins can look it up by type.
- Read configuration and fire or listen to events from anywhere that holds the application.
## Features
- `backpack.New` wires a container around a loaded `*compass.Config`: `backpack.App.Config`, a fresh service registry in `backpack.App.Services` and a new [festival](../festival/README.md) bus in `backpack.App.Events`.
- Typed services keyed by the type argument: `backpack.App.Publish` stores a value under its type T and `backpack.App.Lookup` returns it. Publishing the same T twice or publishing nil is an error, so two plugins cannot silently replace each other's service. Framework modules share their infrastructure this way (for example the database handles published by [lagoon](../lagoon/README.md), or the translator from [phrasebook](../phrasebook/README.md)).
- Plugin presence checks: `backpack.App.SetPlugins` records the complete activated set before any plugin boots, and `backpack.App.HasPlugin` answers whether an optional integration partner is part of this build (the equivalent of WinterCMS's `PluginManager::exists`).
- `backpack.Registry` can be used on its own through `backpack.NewRegistry`; it is safe for concurrent use.
- Nil-safe methods: calls on a nil container or registry return an error or a zero value instead of panicking.
## Usage
```go
package blog
import (
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/compass"
)
// Feed is a service the blog plugin offers to other plugins.
type Feed interface {
Latest(n int) []string
}
type staticFeed struct{}
func (staticFeed) Latest(n int) []string { return []string{"hello-world"} }
func setup() error {
cfg, err := compass.Load("config")
if err != nil {
return err
}
app := backpack.New(cfg)
app.SetPlugins([]string{"acme.blog", "acme.search"})
// Provider side, usually in the plugin's Register step.
var feed Feed = staticFeed{}
if err := app.Publish(feed); err != nil { // published under Feed, not staticFeed
return err
}
// Consumer side, usually in another plugin's Boot step.
if app.HasPlugin("acme.blog") {
if found, ok := app.Lookup[Feed](); ok {
_ = found.Latest(5)
}
}
return nil
}
```
Publish under an interface type when consumers should not depend on the concrete implementation; the lookup must use exactly the same type argument.
## API reference
| Identifier | Description |
|------------|-------------|
| `backpack.App` | The application container: configuration, service registry, event bus and the activated plugin set. |
| `backpack.New` | Creates a `backpack.App` around a loaded configuration, with an empty registry and a new event bus. |
| `backpack.App.Publish` / `backpack.App.Lookup` | Store and retrieve an app-scoped service by type. |
| `backpack.App.SetPlugins` / `backpack.App.HasPlugin` | Record the activated plugin IDs and check whether one is present. |
| `backpack.Registry` | Concurrency-safe typed service catalog behind `backpack.App.Services`. |
| `backpack.NewRegistry` | Returns an empty registry. |
| `backpack.Registry.Publish` / `backpack.Registry.Lookup` | Registry-level publish and lookup behind the `backpack.App` methods. |
## Dependencies
- SummerCMS modules: [compass](../compass/README.md), [festival](../festival/README.md).
- Third-party: none.
- Standard library: `fmt`, `reflect`, `sync`.
## Testing
```sh
go test ./modules/backpack/...
```
The tests build containers in memory and need no external services.