platform

package module
v0.2.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 3, 2026 License: Apache-2.0 Imports: 18 Imported by: 10

README

Platform Documentation

Motivation

The platform package is an extensible, modular system for building HTTP servers and sidecar services in Go.

It provides a global registry for modules and middleware, a lifecycle for graceful shutdown, and named database connections, allowing you to structure services as composable, testable modules.

Application examples, with database use:

Status: the app and maillist packages still need implementation surface.

Coverage

Status Package Coverage Cognitive Lines
titpetric/platform 82.20% 42 370
titpetric/platform/cmd 87.50% 2 23
titpetric/platform/cmd/platform 0.00% 0 3
titpetric/platform/internal 94.87% 20 155
titpetric/platform/pkg/assert 0.00% 0 0
titpetric/platform/pkg/drivers 0.00% 0 0
titpetric/platform/pkg/reflect 100.00% 7 31
titpetric/platform/pkg/require 0.00% 0 0
titpetric/platform/pkg/telemetry 57.44% 8 130
titpetric/platform/pkg/ulid 100.00% 0 20

For more detail, see: Testing Coverage.

Development docs

  • The Platform — key concepts, structure, and lifecycle overview.
  • API documentation - the api documentation for the platform package.
  • Creating Modules — module API, lifecycle, and using UnimplementedModule.
  • Common Patterns — routing, GET/POST, background jobs and validation patterns.
  • SQL Database Usage — named connections, DSN examples, and Connect() vs Open().
  • Telemetry - setting up and using OpenTelemetry.
  • FAQ — short practical answers to common questions.

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.Register` 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 emperative way.

```go svc := platform.New(context.Background()) svg.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.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Error

func Error(w http.ResponseWriter, r *http.Request, status int, data error)

Error writes an error payload as JSON.

func JSON

func JSON(w http.ResponseWriter, r *http.Request, status int, data any)

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 Param added in v0.0.3

func Param(r *http.Request, name string) string

Param will return a named URL parameter, or query string.

func QueryParam added in v0.0.3

func QueryParam(r *http.Request, name string) string

QueryParam will return a named query parameter from the request.

func Register

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.

func Transaction

func Transaction(ctx context.Context, db *sqlx.DB, fn func(context.Context, *sqlx.Tx) error) error

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 URLParam added in v0.0.3

func URLParam(r *http.Request, name string) string

URLParam will return a named parameter value from the request URL.

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. There's no assumption to how the database connection is provided, and the interface supports using external providers, enforces context awareness.

The connection names, singleton behaviour, retries, fallback mechanisms, multiple-name logic and everything else to produce a *sql.DB is left to the implementation. The first party implementation uses the process environment and decodes `PLATFORM_DB_*` and uses the environment variable name for the key and the definition of the connection. It doesn't carry other logic like reconnecting.

It's also likely that the first party database provider will be a public API package in the future. I'd like this to be swappable so people can bring in something like AWS secretsmanager or vault, or other.

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 our JSON choice of an error response.

type ErrorResponseBody

type ErrorResponseBody struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
}

ErrorResponseBody is an inner type for ErrorResponse.Error.

type Middleware

type Middleware func(http.Handler) http.Handler

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

	// Quiet turns down the verbosity in the Platform logging code, set to true in tests.
	Quiet bool

	// Modules controls which modules get loaded. If the list
	// is empty (unconfigured, zero value), all modules load.
	Modules []string
}

Options is a configuration struct for platform behaviour.

func NewOptions

func NewOptions() *Options

NewOptions provides default options for the platform.

func NewTestOptions

func NewTestOptions() *Options

NewTestOptions produces default options for tests.

type Platform

type Platform struct {
	// contains filtered or unexported fields
}

Platform is our world struct.

func FromContext added in v0.0.2

func FromContext(ctx context.Context) *Platform

FromContext returns the *Platform instance attached to the context.

func FromRequest added in v0.0.2

func FromRequest(r *http.Request) *Platform

FromRequest returns the *Platform instance attached to the request.

func New

func New(options *Options) *Platform

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

func Start(ctx context.Context, options *Options) (*Platform, error)

Start is a shorthand to create a new *Platform instance and immediately starts the server listener and handles requests.

func (*Platform) Context

func (p *Platform) Context() context.Context

Context returns the cancellation context for the service. When the context finishes, the server has shut down.

func (*Platform) Find added in v0.0.2

func (p *Platform) Find(target any) bool

Find fills target with the module matching the type.

func (*Platform) Register

func (p *Platform) Register(m Module)

Register will add a registry.Module into the internal platform registry. This function should be called before Serve is called.

func (*Platform) Start

func (p *Platform) Start(ctx context.Context) error

Start will start the server and print the registered routes. It respects cancellation from the passed context, as well as sets up signal notification to respond to SIGTERM.

func (*Platform) Stats

func (p *Platform) Stats() (int, int)

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) URL

func (p *Platform) URL() string

URL gives the e2e endpoint URL for requests.

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.

func (*Platform) Wait

func (p *Platform) Wait()

Wait will pause until the server is shut down.

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

func (r *Registry) Cleanup(fn func(context.Context))

Cleanup is sort of a testing.T.Cleanup but for the registry. The cleanups are initialized in Start, and ran in Close.

func (*Registry) Clone

func (r *Registry) Clone() *Registry

Clone provides a copy of the registry for use in the platform.

func (*Registry) Close

func (r *Registry) Close(ctx context.Context)

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

func (r *Registry) Find(target any) bool

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

func (r *Registry) Register(m Module)

Register adds a Module to the registry.

func (*Registry) Start

func (r *Registry) Start(ctx context.Context, mux Router, opts *Options) error

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.

func (*Registry) Stats

func (r *Registry) Stats() (modules, middleware int)

Stats returns counts for modules and middlewares in the registry.

func (*Registry) Use

func (r *Registry) Use(f Middleware)

Use adds a Middleware to the registry.

type Router

type Router = chi.Router

Router is a local shim that aliases the chi router interface.

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

Mount returns nil (no error).

func (UnimplementedModule) Name

func (m UnimplementedModule) Name() string

Name returns an empty string.

func (UnimplementedModule) Start

Start returns nil (no error).

func (UnimplementedModule) Stop

Stop returns nil (no error).

Directories

Path Synopsis
cmd
platform command
pkg
assert
Package assert provides test assertion helpers re-exported from testify/assert.
Package assert provides test assertion helpers re-exported from testify/assert.
require
Package require provides test requirement helpers re-exported from testify/require.
Package require provides test requirement helpers re-exported from testify/require.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL