Documentation
¶
Overview ¶
The platform is an extensible modular system for writing HTTP servers.
1. Provides a global registry for middleware and module registration 2. Provides a lifecycle to the modules for graceful shutdown 3. Provides a router the modules can attach to
It's advised to use `platform.RegisterFunc` from `init` functions. Similarly, `platform.Use` should be used from `main` or any descendant setup functions. Don't use these functions from tests as they create a shared state.
It's possible to use the platform in an imperative way.
```go svc := platform.New(platform.NewOptions()) svc.Use(middleware.Logger) svc.Register(user.NewModule()) ```
The platform lifecycle is extensively tested to ensure no races, no goroutine leaks. Each platform object creates a copy of the global state and holds scoped allocations only, enabling test parallelism. Modules are part of that copy when registered with `RegisterFunc`, which is called once per platform. A value registered with the deprecated `Register` is shared by every platform in the process.
Index ¶
- func Error(w http.ResponseWriter, r *http.Request, status int, data error)
- func JSON(w http.ResponseWriter, r *http.Request, status int, data any)
- func Param(r *http.Request, name string) string
- func QueryParam(r *http.Request, name string) string
- func Register(m Module)deprecated
- func RegisterFunc(f func() Module)
- func SetupConnections(environment []string)
- func Transaction(ctx context.Context, db *sqlx.DB, fn func(context.Context, *sqlx.Tx) error) error
- func URLParam(r *http.Request, name string) string
- func Use(mw Middleware)
- type DatabaseProvider
- type ErrorResponse
- type ErrorResponseBody
- type Logger
- type Manager
- type Middleware
- type Module
- type Options
- type Platform
- func (p *Platform) Context() context.Context
- func (p *Platform) Find(target any) bool
- func (p *Platform) Register(m Module)
- func (p *Platform) Start(ctx context.Context) error
- func (p *Platform) Stats() (int, int)
- func (p *Platform) Stop()
- func (p *Platform) URL() string
- func (p *Platform) Use(m Middleware)
- func (p *Platform) Wait()
- type Registry
- func (r *Registry) Cleanup(fn func(context.Context))
- func (r *Registry) Clone() *Registry
- func (r *Registry) Close(ctx context.Context)
- func (r *Registry) Find(target any) bool
- func (r *Registry) Register(m Module)deprecated
- func (r *Registry) RegisterFunc(f func() Module)
- func (r *Registry) Start(ctx context.Context, mux Router, opts *Options) error
- func (r *Registry) Stats() (modules, middleware int)
- func (r *Registry) Use(f Middleware)
- type Router
- type TelemetryModule
- type UnimplementedModule
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func JSON ¶
JSON writes any payload as JSON. If the payload is nil, the write is omitted. If an error occurs in encoding, a telemetry error is logged.
func QueryParam ¶ added in v0.0.3
QueryParam will return a named query parameter from the request.
func Register
deprecated
func Register(m Module)
Register will register a module in the platform global registry. It should not be relied upon in tests, keeping global state empty. This enables registering modules using blank imports.
Deprecated: use RegisterFunc. One value is shared by every platform in the process, including the generations of a reload, so its state outlives the platform it was started with.
func RegisterFunc ¶ added in v0.7.1
func RegisterFunc(f func() Module)
RegisterFunc will register a module constructor in the platform global registry. It is called once per platform, so every platform, and every reload generation, starts a module of its own.
It should not be relied upon in tests, keeping global state empty. This enables registering modules using blank imports.
func SetupConnections ¶ added in v0.4.8
func SetupConnections(environment []string)
SetupConnections will parse the env for named connection strings.
func Transaction ¶
Transaction wraps a function in a transaction. If the function returns an error, the transaction is rolled back. If the function returns nil, the transaction is committed.
func Use ¶
func Use(mw Middleware)
Use will add a middleware to the platform router. It should not be relied upon in tests, keeping global state empty. This should be used from main() to define any global middleware.
Types ¶
type DatabaseProvider ¶
type DatabaseProvider interface {
// Open takes a context, a different implementation may use it for context awareness.
// For example, Open may retrieve connection details from the database. The context
// is used for tracing that operation, and the error returned may be the
// result of a query issued against the database.
Open(ctx context.Context, names ...string) (*sqlx.DB, error)
// Connect has the semantics of Open + PingContext against the database,
// verifying that the connection is live. An error is returned if
// the storage is unreachable.
//
// A real database provider options may dictate additional behaviour.
// For example, if a connection fails, it may retry a number of times,
// it may back-off, it may continue to retry the connection in a
// blocking way. It may also grab additional fail-over information from
// keys based on the input parameters. An example of that would be to
// define `name` and `name/failover` connections. If the connection to
// the first fails, the second upstream is used.
Connect(ctx context.Context, names ...string) (*sqlx.DB, error)
}
DatabaseProvider is the implementation interface for working with named connections. If no connection name is passed, the "default" connection will be used. It makes no assumption about how the connection is provided, and only enforces context awareness.
Connection names, singleton behaviour, retries, fallback mechanisms, multiple-name logic and everything else needed to produce a *sql.DB is left to the implementation. The first party one decodes `PLATFORM_DB_*` from the process environment, using the variable name as the key and its value as the connection definition, and carries no other logic like reconnecting.
var Database DatabaseProvider = global.db
Database is a holder of the database provider api in package namespace.
type ErrorResponse ¶
type ErrorResponse struct {
Error ErrorResponseBody `json:"error"`
}
ErrorResponse is the JSON structure for error responses.
type ErrorResponseBody ¶
ErrorResponseBody is an inner type for ErrorResponse.Error.
type Logger ¶ added in v0.7.1
Logger is the interface the platform writes its own output through. It is the subset of *slog.Logger the platform needs, so a *slog.Logger can be assigned to Platform.Logger as it is.
type Manager ¶ added in v0.7.1
type Manager struct {
// Logger receives the manager's own output, and is handed to every
// platform generation it creates. Defaults as Platform.Logger does.
Logger Logger
// Setup runs against every platform generation before it starts.
// Registration against a platform value belongs here, as a reload
// discards the value it was made against. Assign it before Start: a
// reload runs it from the signal handler's goroutine.
Setup func(*Platform) error
// Check runs before a reload retires the generation that is serving,
// and a non-nil error abandons that reload with the generation left
// alone. Reading the configuration a reload would apply belongs here:
// Setup runs against the new generation, which exists only once the old
// one has stopped. Nil reloads unconditionally. Assign before Start, as
// with Setup, because the signal handler reads it from a goroutine.
Check func() error
// contains filtered or unexported fields
}
Manager owns the socket, and the platform generation serving on it. It replaces the platform under it on SIGHUP, keeping the address it serves.
func NewManager ¶ added in v0.7.1
NewManager creates a manager for the passed options. If no options are passed, the defaults from NewOptions() are in use.
func (*Manager) Context ¶ added in v0.7.1
Context returns the cancellation context for the manager. When the context finishes, the platform has shut down and the socket is closed.
func (*Manager) Platform ¶ added in v0.7.1
Platform returns the generation currently serving, and nil when there is none: before Start, after Stop, and after a reload that failed.
func (*Manager) Reload ¶ added in v0.7.1
Reload stops the running platform and starts a new one on the same socket, unless Check refuses it first. Generations never overlap, so a module registered as a value has to survive a restart.
func (*Manager) Start ¶ added in v0.7.1
Start binds the listener, starts the first platform generation on it, writes Options.PidFile and arms the SIGHUP handler. Cancelling ctx stops the manager.
func (*Manager) Stop ¶ added in v0.7.1
func (m *Manager) Stop()
Stop shuts down the running platform and closes the socket. A stopped manager does not start again.
type Middleware ¶
Middleware is a type alias for middleware functions.
func TestMiddleware ¶
func TestMiddleware() Middleware
TestMiddleware returns a middleware that just passes along the request.
type Module ¶
type Module interface {
// Name should return a meaningful name for your module.
Name() string
// Start is used to create any goroutines or otherwise
// set up the module by starting a server. It allows
// to implement a lifecycle of the service.
Start(context.Context) error
// Stop should clean up any goroutines, clean up leaks.
Stop(context.Context) error
// Mount runs before the server starts, and allows you to
// register new routes to your module.
Mount(context.Context, Router) error
}
Module is the implementation contract for modules.
The interface should only be used to enforce the API contract as shown below. It's also used to provide `platform.Register()`.
type Options ¶
type Options struct {
// ServerAddr is the address the server listens to.
ServerAddr string
// PidFile is the file the process records its own id in, for a service
// manager or a command line that signals it. Empty writes none. The
// directory has to exist, an existing file is overwritten, and the file
// is removed on a clean stop. A Manager writes it for the process, so a
// reload leaves it alone.
PidFile string
// Quiet silences the platform's own output: New installs a discarding
// logger as Platform.Logger instead of the default one. Set to true in
// tests. Assigning Platform.Logger afterwards overrules it.
Quiet bool
// Modules controls which modules get loaded. If the list
// is empty (unconfigured, zero value), all modules load.
Modules []string
// ConfigFS can be used for configuration purposes by modules. It's optional and may be nil.
// The application running with the platform may use `go:embed` to carry config for the
// composed service.
ConfigFS fs.FS
// Telemetry configures the recorder and the debug dashboard. It is
// off unless asked for: the zero value disables it, and NewOptions
// disables it too, because the dashboard reports the internals of the
// process and is unauthenticated unless Telemetry.Authorize, its
// network allow list or its users say otherwise. Set Enabled, or
// PLATFORM_TELEMETRY_ENABLED, to record.
Telemetry oida.Options
}
Options is a configuration struct for platform behaviour.
func NewTestOptions ¶
func NewTestOptions() *Options
NewTestOptions produces default options for tests.
func OptionsFromContext ¶ added in v0.3.4
OptionsFromContext returns the *Options instance attached to the context.
func OptionsFromRequest ¶ added in v0.3.4
OptionsFromRequest returns the *Options instance attached to the request.
type Platform ¶
type Platform struct {
// Logger receives the platform's own output. New sets it to
// slog.Default(), or to a discarding logger when Options.Quiet is set.
// Assign to it before Start to reuse the platform logger from a test,
// or to route output into a consumer application's logger.
Logger Logger
// contains filtered or unexported fields
}
Platform is our world struct.
func FromContext ¶ added in v0.0.2
FromContext returns the *Platform instance attached to the context.
func FromRequest ¶ added in v0.0.2
FromRequest returns the *Platform instance attached to the request.
func New ¶
New will create a new *Platform object. It is the allocation point for each platform instance. If no options are passed, the defaults are in use. The defaults options are provided by NewOptions().
func Start ¶
Start is a shorthand to create a new *Platform instance and immediately starts the server listener and handles requests.
func (*Platform) Context ¶
Context returns the cancellation context for the service. When the context finishes, the server has shut down.
func (*Platform) Register ¶
Register will add a registry.Module into the internal platform registry. This function should be called before Serve is called.
func (*Platform) Start ¶
Start starts the server, writes Options.PidFile and prints the registered routes. It stops on a cancelled context, SIGINT or SIGTERM.
func (*Platform) Stats ¶
Stats will report how many middlewares and plugins are added to the registry.
func (*Platform) Stop ¶
func (p *Platform) Stop()
Stop will gracefully shutdown the server and then cancel the server context when done.
Stop is an important part of the lifecycle tests. When closing the registry, each plugins Stop function gets invoked in parallel. This enables the plugin to clear background goroutine event loops, or flush a dirty buffer to storage.
Only after the server has fully shut down does the internal context get cancelled.
func (*Platform) Use ¶
func (p *Platform) Use(m Middleware)
Use will add a middleware to the internal platform registry. This function should be called before Serve is called.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry provides a programmatic API to manage middleware and modules. A module registers middleware and has a contract to enforce lifecycle.
func (*Registry) Cleanup ¶ added in v0.2.1
Cleanup registers a function to run when the registry closes, as testing.T.Cleanup does for a test. The cleanups are registered in Start, and run in Close.
func (*Registry) Clone ¶
Clone provides a copy of the registry for use in the platform. Modules registered as constructors are built here, one set per clone.
func (*Registry) Close ¶
Close will invoke all the modules close functions in parallel. When finished, it will clear the registered modules list, as well as any defined middleware and invoked cleanups.
func (*Registry) Find ¶ added in v0.0.2
Find gets a Module from the registry. The target argument can be a pointer or an interface. The function returns true if a module matching the type or interface was found and assigned to `target`.
func (*Registry) Register
deprecated
func (*Registry) RegisterFunc ¶ added in v0.7.1
RegisterFunc adds a module constructor to the registry. Clone calls it, so every platform starts a module of its own.
func (*Registry) Start ¶
Start will invoke all the modules start functions sequentially. If an error occurs, execution is halted and an error is returned. The context is passed along for observability and access to the platform. The registry's own output goes to the logger of the platform in the context; without one it is discarded.
type TelemetryModule ¶ added in v0.6.0
type TelemetryModule struct {
UnimplementedModule
// contains filtered or unexported fields
}
TelemetryModule records the telemetry of a platform service. It is the recorder and the dashboard at once: HTTP middleware recording every request, and a module mounting the debug front end under Options.Path.
New registers one by default. A host that wires its own recorder disables Options.Telemetry, so only one dashboard is on the path and only one middleware records the request.
func NewTelemetryModule ¶ added in v0.6.0
func NewTelemetryModule(options oida.Options) (*TelemetryModule, error)
NewTelemetryModule returns a telemetry module recording into its own tracer. The tracer is explicit rather than the process wide one, so two services, or two tests, do not record into each other.
func (*TelemetryModule) Middleware ¶ added in v0.6.0
func (m *TelemetryModule) Middleware(next http.Handler) http.Handler
Middleware records requests handled by next.
func (*TelemetryModule) Mount ¶ added in v0.6.0
func (m *TelemetryModule) Mount(_ context.Context, r Router) error
Mount registers the debug front end on the platform router. The tracer is an http.Handler serving its own dashboard, and oida.Mount adds the subtree patterns each router understands.
func (*TelemetryModule) Options ¶ added in v0.6.0
func (m *TelemetryModule) Options() oida.Options
Options returns the options the tracer runs on, which is what the module was built with after the environment was applied. The retention driver is left out of the copy; a caller that needs it holds the storage it configured.
func (*TelemetryModule) Tracer ¶ added in v0.6.0
func (m *TelemetryModule) Tracer() *oida.Tracer
Tracer returns the tracer the module records into.
type UnimplementedModule ¶
type UnimplementedModule struct {
NameFn func() string
StartFn func(context.Context) error
StopFn func(context.Context) error
MountFn func(context.Context, Router) error
}
UnimplementedModule implements the module contract. The module can embed the type to skip implementing any of the bound functions.
func NewUnimplementedModule ¶ added in v0.2.1
func NewUnimplementedModule(name string) *UnimplementedModule
NewUnimplementedModule will fill the module name.
func (UnimplementedModule) Mount ¶
func (m UnimplementedModule) Mount(ctx context.Context, r Router) error
Mount returns nil (no error).
func (UnimplementedModule) Name ¶
func (m UnimplementedModule) Name() string
Name returns an empty string.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
platform
command
|
|
|
assert
Package assert is a minimalistic drop-in replacement for testify/assert.
|
Package assert is a minimalistic drop-in replacement for testify/assert. |
|
pidfile
Package pidfile records a process id in a file, for a service manager or a command line that signals the process later.
|
Package pidfile records a process id in a file, for a service manager or a command line that signals the process later. |