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 ¶
- 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)
- 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 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)
- 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 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 ¶
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 ¶
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. 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 ¶
ErrorResponseBody is an inner type for ErrorResponse.Error.
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
// 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 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
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 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 ¶
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 is sort of a testing.T.Cleanup but for the registry. The cleanups are initialized in Start, and ran in Close.
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) 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.
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
|
|
|
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. |