Documentation
¶
Overview ¶
Package bootstrap is the boot sequence: what runs once, in order, before the application answers anything.
It composes the components; it is not one of them, which is why it lives here rather than with them.
Two bootstrappers run, and they are the whole of it:
LoadConfiguration reads the environment, .env included, and answers every
component's settings
HandleExceptions builds the handler that answers when something fails
Loading the environment is not a bootstrapper of its own. Nothing here is evaluated -- the reading is direct, in LoadConfiguration -- so a second one would be a second way to load one file.
Registering and booting modules are not bootstrappers either. There is no container and no provider: what an application composes is modules, explicitly, and the two halves of that are Application.Register and Application.Boot. They are methods rather than bootstrappers because the list of modules is written by hand in bootstrap/app.go, where it can be read.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func HandleExceptions ¶
func HandleExceptions(cfg Configuration, appModule string, diagnose func(context.Context) []string) *exception.Handler
HandleExceptions builds the handler that answers when something fails.
It installs no process-wide hook and needs none. An error is a value that travels back through the call stack, and the one thing that does escape -- a panic -- is caught by exception.Recover, a middleware, at a place in the pipeline that is visible in bootstrap/app.go. So this bootstrapper builds the handler and returns it; installing it is the application wiring it, not a side effect nobody can see.
What the AppModule is for, and why it is not guessed ¶
The debug page separates the frames of your code from the frames of the framework, and it tells them apart by module path prefix. Passing it in is what makes that work in a project whose module is example.test/loja.
A hard-coded constant cannot do it. With the implementation in the collection, every hesape frame would read as application code on the one screen where being wrong costs the most.
Types ¶
type Configuration ¶
type Configuration struct {
// App is the application itself: name, environment, key, URL, locale.
// It is the only one with a loader of its own, because hesape/config.Load
// already validates the key and refuses debug in production.
App config.App
Session session.Config
Cache cache.Config
Database database.Config
Log log.Config
Filesystem filesystem.Config
Queue queue.Config
View view.Config
// Observability is what the assembled application needs to explain itself.
// It is here, beside the components, and not one of them -- see the type.
Observability Observability
// Repository answers the components that read configuration through an
// interface rather than a struct -- hashing.Config is one, and it is three
// keys and one method by design, so that hesape/hashing does not import a
// configuration package to read them.
//
// It is a reader over the same settings, never a second store. Nothing the
// framework depends on is read through it, and a key set here and nowhere
// else configures nothing.
Repository *config.Repository
}
Configuration is every component's settings, typed, built once at boot.
It is not a config file and it is not a registry. Each component declares its own Config in its own package, because without a container nothing looks a value up by key -- the component is handed what it needs and the compiler checks the field. This struct is the one place that reads the environment and fills them in.
The difference from a Repository of dotted keys is where a mistake surfaces. A wrong field here does not compile. A wrong key in a map compiles, returns the zero value, and shows up on the first request that happened to need it.
Why the components are not asked to load themselves ¶
Every field below could have been a Load() in its own package, and that is one Load per component reading the same environment at different moments. Boot order would stop being visible, a variable read twice could answer twice, and the failure of any of them would arrive whenever that component was first touched rather than at start. One reader, one moment, one error.
func LoadConfiguration ¶
func LoadConfiguration() (Configuration, error)
LoadConfiguration reads the environment once and answers every component's settings.
It fails the process rather than returning a half-built Configuration. A framework that boots with a missing key and discovers it on the first request has moved a start-up error into production traffic.
The variable names are the ones a .env already carries -- APP_NAME, APP_KEY, DB_CONNECTION and the rest keep the spelling and the meaning they have elsewhere, so a file moved across works unchanged. Where a default differs, the field says so.
Where a default is not the obvious one, the field says why.
type Observability ¶ added in v0.27.0
type Observability struct {
// LogLevel is what the root logger keeps.
//
// It comes from LOG_LEVEL, spelled as one of the eight level names, and it
// is the level of the logger the application is built with -- not of a
// channel. A channel declares its own under Log.Channels; the root has no
// channel to inherit one from, so the variable is read here as well, once,
// and parsed into the type the logger takes.
//
// Debug is refused in production, where a request's arguments end up in the
// log of a system holding customer data.
LogLevel slog.Level
// TracingSecret opens the debug console outside development, to a request
// carrying it in the tracing header and to nothing else.
//
// Empty is the default and disables it. Tracing is opt-in per deployment: a
// console that answers because nobody set a variable is a buffer of SQL,
// bound arguments and dumps, across every tenant, reachable with no session.
TracingSecret string
// Editor is what the "open in IDE" links on the error page and the console
// open. One of vscode, cursor, goland, zed.
Editor string
}
Observability is how the assembled application reports on itself: what the root logger keeps, who may open the debug console, and where a stack frame links to.
It is the one part of Configuration that is not a component's own Config, and the reason is what the three fields have in common: none of them configures a component. The channels, the handlers and the format belong to Log, and a channel carries its own level. These three belong to the application that was assembled -- they decide the cut of the root logger, whether the console answers at all outside development, and what an "open in IDE" link opens.