Documentation
¶
Overview ¶
Package forge manages the lifecycle of a set of transport servers as one application.
New builds an App from options: identity (WithID, WithName, WithVersion, WithMetadata), the servers to run (WithServer), an optional service registry (WithRegistrar), and lifecycle hooks (WithBeforeStart, WithAfterStart, WithBeforeStop, WithAfterStop). App.Run starts every server, registers the instance, and blocks until a stop signal arrives or App.Stop is called; on the way out it deregisters, drains the servers within WithStopTimeout, and runs the AfterStop hooks within WithAfterStopTimeout. Run joins every error it observed rather than reporting only the first.
A Suite bundles options that belong together — an integration and its hooks — so an application adopts them with a single WithSuite call. Handlers and hooks recover the application's identity from the context via FromContext.
The package dictates no project layout and ships no scaffolding CLI. See docs/agent/application.md for the usage contract and docs/design/application-lifecycle.md for the lifecycle rationale.
Example (FromContext) ¶
Example_fromContext mirrors the guide's identity snippet: inside a handler or hook, recover application identity from the context.
package main
import (
"context"
"fmt"
"github.com/sylphylabs/forge"
)
func main() {
app := forge.New(forge.WithName("helloworld"))
ctx := forge.NewContext(context.Background(), app)
if info, ok := forge.FromContext(ctx); ok {
fmt.Println(info.Name())
_ = info.Endpoints()
}
}
Output: helloworld
Example (Lifecycle) ¶
Example_lifecycle mirrors "Lifecycle": hooks and the three independent timeouts are all construction options.
package main
import (
"context"
"fmt"
"time"
"github.com/sylphylabs/forge"
"github.com/sylphylabs/forge/registry"
forgehttp "github.com/sylphylabs/forge/transport/http"
)
// noopRegistrar stands in for a registry integration (contrib/registry/...).
type noopRegistrar struct{}
func (noopRegistrar) Register(context.Context, *registry.ServiceInstance) error { return nil }
func (noopRegistrar) Deregister(context.Context, *registry.ServiceInstance) error { return nil }
// pool stands in for the guide's application-owned resource whose lifecycle
// the hooks manage.
type pool struct{}
func (pool) Ping(context.Context) error { return nil }
func (pool) Close() error { return nil }
func main() {
httpSrv := forgehttp.NewServer(forgehttp.WithAddress("127.0.0.1:0"))
var reg noopRegistrar
var dbPool pool
app := forge.New(
forge.WithName("helloworld"),
forge.WithServer(httpSrv),
forge.WithRegistrar(reg),
forge.WithRegistrarTimeout(5*time.Second),
forge.WithStopTimeout(15*time.Second),
forge.WithAfterStopTimeout(5*time.Second),
forge.WithBeforeStart(func(ctx context.Context) error { return dbPool.Ping(ctx) }),
forge.WithAfterStop(func(_ context.Context) error { return dbPool.Close() }),
)
_ = app
fmt.Println("constructed")
}
Output: constructed
Example (MinimalService) ¶
Example_minimalService mirrors "Minimal service". It has no Output comment, so `go test` compiles it without running it: the guide's version blocks in Run until a stop signal arrives.
package main
import (
"github.com/sylphylabs/forge"
forgegrpc "github.com/sylphylabs/forge/transport/grpc"
forgehttp "github.com/sylphylabs/forge/transport/http"
)
func main() {
httpSrv := forgehttp.NewServer(forgehttp.WithAddress(":8000"))
grpcSrv := forgegrpc.NewServer(forgegrpc.WithAddress(":9000"))
app := forge.New(
forge.WithName("helloworld"),
forge.WithVersion("v1.0.0"),
forge.WithServer(httpSrv, grpcSrv),
)
if err := app.Run(); err != nil {
panic(err)
}
}
Output:
Example (Suite) ¶
package main
import (
"context"
"fmt"
"github.com/sylphylabs/forge"
)
// pool stands in for the guide's application-owned resource whose lifecycle
// the hooks manage.
type pool struct{}
func (pool) Ping(context.Context) error { return nil }
func (pool) Close() error { return nil }
// closingSuite mirrors the guide's tracing suite: an integration bundles its
// options — here an AfterStop hook that shuts the integration down — so an
// application adopts them in one WithSuite call. The guide's version closes
// an OpenTelemetry TracerProvider; the shape is the same for any resource.
type closingSuite struct{ closer interface{ Close() error } }
func (s closingSuite) Options() []forge.Option {
return []forge.Option{
forge.WithAfterStop(func(_ context.Context) error {
return s.closer.Close()
}),
}
}
func main() {
app := forge.New(
forge.WithName("helloworld"),
forge.WithSuite(closingSuite{closer: pool{}}),
)
_ = app
fmt.Println("constructed")
}
Output: constructed
Index ¶
- func AppProbe(info AppInfo) diagnosis.ProbeFunc
- func NewContext(ctx context.Context, s AppInfo) context.Context
- type App
- type AppInfo
- type AppSnapshot
- type Option
- func WithAfterStart(fn func(context.Context) error) Option
- func WithAfterStop(fn func(context.Context) error) Option
- func WithAfterStopTimeout(t time.Duration) Option
- func WithBeforeStart(fn func(context.Context) error) Option
- func WithBeforeStop(fn func(context.Context) error) Option
- func WithContext(ctx context.Context) Option
- func WithEndpoint(endpoints ...*url.URL) Option
- func WithID(id string) Option
- func WithLogger(logger *slog.Logger) Option
- func WithMetadata(md map[string]string) Option
- func WithName(name string) Option
- func WithRegistrar(r registry.Registrar) Option
- func WithRegistrarTimeout(t time.Duration) Option
- func WithServer(srv ...transport.Server) Option
- func WithSignal(sigs ...os.Signal) Option
- func WithStopTimeout(t time.Duration) Option
- func WithSuite(s Suite) Option
- func WithVersion(version string) Option
- type Suite
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func AppProbe ¶
AppProbe returns a probe that reports info's identity as an AppSnapshot.
The probe reads info when it runs, not when it is built, so a snapshot taken after App.Run has started the servers includes the endpoints they bound. Register it under a name of your choosing — "app" by convention:
reg := diagnosis.NewRegistry()
app := forge.New(forge.WithName("checkout"), ...)
reg.Register("app", forge.AppProbe(app))
AppProbe panics if info is nil; a probe wired to nothing is a construction bug, surfaced at the offending line rather than as an error in every dump.
Types ¶
type App ¶
type App struct {
// contains filtered or unexported fields
}
App is an application components lifecycle manager.
func (*App) Healthz ¶
Healthz reports whether every server that exposes readiness through transport.Healthzer can accept new work. Servers without the capability make no claim and do not affect the result. App itself satisfies transport.Healthzer, so it can feed a health endpoint directly.
func (*App) Run ¶
Run starts the application and blocks until it stops. It builds the registry instance, runs the BeforeStart hooks, starts every configured server, registers the instance with the registrar when one is configured, and then runs the AfterStart hooks. It returns when the application stops — because App.Stop was called, an exit signal from the WithSignal option arrived, or a server or hook failed. On the way out it runs the BeforeStop hooks, deregisters the instance, shuts the servers down within WithStopTimeout, and finally runs the AfterStop hooks within WithAfterStopTimeout. The returned error joins every failure observed along the way.
type AppInfo ¶
type AppInfo interface {
ID() string
Name() string
Version() string
Metadata() map[string]string
Endpoints() []string
}
AppInfo is application context value.
type AppSnapshot ¶
type AppSnapshot struct {
// ID is the application instance id.
ID string `json:"id"`
// Name is the service name.
Name string `json:"name"`
// Version is the application version.
Version string `json:"version"`
// Metadata is the service metadata, if any.
Metadata map[string]string `json:"metadata,omitempty"`
// Endpoints lists the endpoints the application currently advertises.
// Empty before the application has started its servers.
Endpoints []string `json:"endpoints,omitempty"`
}
AppSnapshot is the value reported by an AppProbe: the application's identity as it stands at the moment the probe runs.
type Option ¶
type Option func(o *options)
Option is an application option.
Options come in two shapes. Scalar options (WithID, WithName, WithVersion, WithContext, WithLogger, WithRegistrar, and the timeouts) set a single field: when the same option appears more than once, the one applied last wins. Collection options (WithServer, WithEndpoint, WithSignal, WithMetadata, and the lifecycle hooks) accumulate: every application of the option adds to what earlier applications contributed, so independent option lists — for example two Suite values — compose without overwriting each other.
func WithAfterStart ¶
WithAfterStart registers a func to run after the app starts.
func WithAfterStop ¶
WithAfterStop registers a func to run after the app stops.
func WithAfterStopTimeout ¶
WithAfterStopTimeout sets the total time allowed for all AfterStop hooks. A non-positive duration disables the deadline.
func WithBeforeStart ¶
WithBeforeStart registers a func to run before the app starts.
func WithBeforeStop ¶
WithBeforeStop registers a func to run before the app stops.
func WithContext ¶
WithContext sets the service context.
func WithEndpoint ¶
WithEndpoint appends endpoints to the service endpoints.
func WithMetadata ¶
WithMetadata merges md into the service metadata. Keys from later applications win over earlier ones.
func WithRegistrar ¶
WithRegistrar sets the service registrar.
func WithRegistrarTimeout ¶
WithRegistrarTimeout sets the registrar timeout.
func WithServer ¶
WithServer appends transport servers to the application.
func WithSignal ¶
WithSignal appends exit signals to the set the application stops on. When no WithSignal option is given, the application stops on SIGTERM, SIGQUIT, and SIGINT.
func WithStopTimeout ¶
WithStopTimeout sets the app stop timeout.
func WithSuite ¶
WithSuite expands a Suite into a single Option. The suite's options apply in place, exactly where the returned Option appears in the caller's option list, in the order Options returned them. Each option keeps its usual semantics whether it came from a suite or was written directly: scalar options such as WithName, WithVersion, WithID, WithContext, and WithLogger set a single field and the one applied last wins, while collection options such as WithServer, WithEndpoint, WithMetadata, and the lifecycle hooks accumulate, so independent suites contributing to the same collection all take effect.
WithSuite calls Options once, immediately, so a suite is read at the point it is wired, not when the application is constructed. It panics right away if s is nil or if Options returns a nil element; a broken wiring fails at the offending line during construction rather than surfacing later.
type Suite ¶
type Suite interface {
// Options returns the options this suite contributes, in the order they
// should apply. Every element must be non-nil.
Options() []Option
}
Suite bundles application options that belong together. An integration — a service registry plus its lifecycle hooks, a logging setup plus the metadata it reads — implements Suite once, and an application adopts the whole bundle with a single WithSuite call instead of repeating each option.
A Suite carries no state of its own inside the application: everything it contributes goes through the returned options, so two applications may hold differently configured instances of the same Suite type without sharing anything. Suites compose: the slice returned by Options may itself contain options produced by WithSuite, and independent suites written without knowledge of each other stack in a single option list.
Directories
¶
| Path | Synopsis |
|---|---|
|
api
module
|
|
|
Package auth carries the identity of the caller in flight.
|
Package auth carries the identity of the caller in flight. |
|
cmd
module
|
|
|
Package config loads configuration from pluggable sources, merges the results into one key tree, and keeps that tree current as sources change.
|
Package config loads configuration from pluggable sources, merges the results into one key tree, and keeps that tree current as sources change. |
|
env
Package env sources configuration from process environment variables, optionally filtered and trimmed by prefix.
|
Package env sources configuration from process environment variables, optionally filtered and trimmed by prefix. |
|
file
Package file sources configuration from a file or every non-hidden file in a directory, inferring each payload's format from the file extension.
|
Package file sources configuration from a file or every non-hidden file in a directory, inferring each payload's format from the file extension. |
|
contrib
|
|
|
message/nats
module
|
|
|
middleware/validate
module
|
|
|
otel
module
|
|
|
Package diagnosis gives components one shared way to expose internal state for inspection.
|
Package diagnosis gives components one shared way to expose internal state for inspection. |
|
Package encoding defines the Codec contract — Marshal, Unmarshal, and a stable Name — and a process-wide registry that resolves a codec by the content subtype it serves.
|
Package encoding defines the Codec contract — Marshal, Unmarshal, and a stable Name — and a process-wide registry that resolves a codec by the content subtype it serves. |
|
form
Package form provides the URL-encoded form encoding.Codec, handling both plain Go structs (via go-playground/form) and Protobuf messages (field-name aware, honoring json_name).
|
Package form provides the URL-encoded form encoding.Codec, handling both plain Go structs (via go-playground/form) and Protobuf messages (field-name aware, honoring json_name). |
|
json
Package json provides the JSON encoding.Codec, backed by encoding/json.
|
Package json provides the JSON encoding.Codec, backed by encoding/json. |
|
proto
Package proto defines the protobuf codec.
|
Package proto defines the protobuf codec. |
|
protojson
Package protojson provides the ProtoJSON encoding.Codec for Protobuf messages, backed by google.golang.org/protobuf/encoding/protojson.
|
Package protojson provides the ProtoJSON encoding.Codec for Protobuf messages, backed by google.golang.org/protobuf/encoding/protojson. |
|
xml
Package xml provides the XML encoding.Codec, backed by encoding/xml.
|
Package xml provides the XML encoding.Codec, backed by encoding/xml. |
|
yaml
Package yaml provides the YAML encoding.Codec.
|
Package yaml provides the YAML encoding.Codec. |
|
Package errors defines Forge's error contract: a portable representation that carries a failure's category, identity, and cause across process boundaries without losing meaning.
|
Package errors defines Forge's error contract: a portable representation that carries a failure's category, identity, and cause across process boundaries without losing meaning. |
|
internal
|
|
|
backstop
Package backstop converts a panic caught at a transport boundary into the generic internal error every transport puts on the wire for one.
|
Package backstop converts a panic caught at a transport boundary into the generic internal error every transport puts on the wire for one. |
|
e2e/service
command
Command service is the Forge server used by the cross-service end-to-end test.
|
Command service is the Forge server used by the cross-service end-to-end test. |
|
formtag
Package formtag holds the struct tag used to bind URL values.
|
Package formtag holds the struct tag used to bind URL values. |
|
group
Package group provides a sample lazy load container.
|
Package group provides a sample lazy load container. |
|
protojsonutil
Package protojsonutil centralizes the protojson marshal/unmarshal policy for packages that already carry a protobuf dependency (config, encoding/form).
|
Package protojsonutil centralizes the protojson marshal/unmarshal policy for packages that already carry a protobuf dependency (config, encoding/form). |
|
Package log builds structured loggers on the standard library's log/slog.
|
Package log builds structured loggers on the standard library's log/slog. |
|
Package metadata carries request-scoped key-value pairs across process boundaries, transport-neutrally.
|
Package metadata carries request-scoped key-value pairs across process boundaries, transport-neutrally. |
|
Package middleware defines the two middleware contracts every Forge transport composes: unary and stream.
|
Package middleware defines the two middleware contracts every Forge transport composes: unary and stream. |
|
circuitbreaker
Package circuitbreaker provides client middleware that stops calling an operation whose recent attempts have been failing, so a struggling dependency gets headroom to recover instead of more load.
|
Package circuitbreaker provides client middleware that stops calling an operation whose recent attempts have been failing, so a struggling dependency gets headroom to recover instead of more load. |
|
governance
Package governance turns middleware parameters into dynamically observable values instead of construction-time constants.
|
Package governance turns middleware parameters into dynamically observable values instead of construction-time constants. |
|
logging
Package logging provides middleware that writes one structured record per request: operation, transport kind, request summary, latency, and — on failure — the error's kind, reason, domain, and trace ID.
|
Package logging provides middleware that writes one structured record per request: operation, transport kind, request summary, latency, and — on failure — the error's kind, reason, domain, and trace ID. |
|
metadata
Package metadata provides middleware that moves application metadata (package github.com/sylphylabs/forge/metadata) between the context and the transport headers, so request-scoped values propagate across process boundaries without transport-specific code.
|
Package metadata provides middleware that moves application metadata (package github.com/sylphylabs/forge/metadata) between the context and the transport headers, so request-scoped values propagate across process boundaries without transport-specific code. |
|
ratelimit
Package ratelimit provides server middleware that sheds load when the service is over capacity, failing rejected requests fast with ErrLimitExceed (KindResourceExhausted) instead of queueing them into timeouts.
|
Package ratelimit provides server middleware that sheds load when the service is over capacity, failing rejected requests fast with ErrLimitExceed (KindResourceExhausted) instead of queueing them into timeouts. |
|
recovery
Package recovery provides middleware that recovers a panicking handler, logs the panic value with its stack, and converts the panic into an error the transport can serve.
|
Package recovery provides middleware that recovers a panicking handler, logs the panic value with its stack, and converts the panic into an error the transport can serve. |
|
retry
Package retry provides client middleware that re-invokes a failed unary call, with an injectable backoff curve — exponential full jitter by default — and a per-operation policy that can be governed at runtime.
|
Package retry provides client middleware that re-invokes a failed unary call, with an injectable backoff curve — exponential full jitter by default — and a per-operation policy that can be governed at runtime. |
|
selector
Package selector provides middleware that applies other middleware conditionally, by matching the operation of the call in flight.
|
Package selector provides middleware that applies other middleware conditionally, by matching the operation of the call in flight. |
|
throws
Package throws asserts at runtime that the error identities leaving a method are the ones its Protobuf throws declarations promised.
|
Package throws asserts at runtime that the error identities leaving a method are the ones its Protobuf throws declarations promised. |
|
timeout
Package timeout provides server middleware that bounds handler execution time, with a per-operation deadline that can be governed at runtime.
|
Package timeout provides server middleware that bounds handler execution time, with a per-operation deadline that can be governed at runtime. |
|
validate
Package validate provides server middleware that rejects invalid requests before the handler runs.
|
Package validate provides server middleware that rejects invalid requests before the handler runs. |
|
Package registry defines the service registration and discovery contract.
|
Package registry defines the service registration and discovery contract. |
|
Package selector picks one node from a discovered set for each request: load balancing on the client side of Forge's HTTP and gRPC transports.
|
Package selector picks one node from a discovered set for each request: load balancing on the client side of Forge's HTTP and gRPC transports. |
|
filter
Package filter provides ready-made node filters for selector.Selector Select calls.
|
Package filter provides ready-made node filters for selector.Selector Select calls. |
|
node/direct
Package direct wraps nodes with their statically declared weight: the weight discovery published is the weight the balancer sees, with no runtime feedback.
|
Package direct wraps nodes with their statically declared weight: the weight discovery published is the weight the balancer sees, with no runtime feedback. |
|
node/ewma
Package ewma wraps nodes with exponentially weighted moving averages of their observed latency and success rate, so a balancer can prefer nodes that are currently fast and healthy over ones that are merely declared heavy.
|
Package ewma wraps nodes with exponentially weighted moving averages of their observed latency and success rate, so a balancer can prefer nodes that are currently fast and healthy over ones that are merely declared heavy. |
|
p2c
Package p2c provides a "power of two choices" selector: each pick compares two random nodes by their EWMA-tracked latency and health and takes the better one, which balances load with O(1) work per pick.
|
Package p2c provides a "power of two choices" selector: each pick compares two random nodes by their EWMA-tracked latency and health and takes the better one, which balances load with O(1) work per pick. |
|
random
Package random provides a uniformly random selector: every pick chooses among the candidates with equal probability, ignoring weights.
|
Package random provides a uniformly random selector: every pick chooses among the candidates with equal probability, ignoring weights. |
|
wrr
Package wrr provides a weighted round robin selector: nodes are picked in proportion to their declared weights, with no runtime feedback.
|
Package wrr provides a weighted round robin selector: nodes are picked in proportion to their declared weights, with no runtime feedback. |
|
Package transport defines the contract between the application lifecycle and the servers it runs, and the call-scoped view middleware gets of the transport underneath it.
|
Package transport defines the contract between the application lifecycle and the servers it runs, and the call-scoped view middleware gets of the transport underneath it. |
|
grpc
Package grpc provides Forge's gRPC transport: a server wrapping google.golang.org/grpc with Forge's lifecycle and middleware contracts, and a client constructor that wires the same contracts into a *grpc.ClientConn.
|
Package grpc provides Forge's gRPC transport: a server wrapping google.golang.org/grpc with Forge's lifecycle and middleware contracts, and a client constructor that wires the same contracts into a *grpc.ClientConn. |
|
http
Package http provides Forge's HTTP transport: a server that generated service code registers routes on, and a client that generated code calls through.
|
Package http provides Forge's HTTP transport: a server that generated service code registers routes on, and a client that generated code calls through. |
|
http/healthz
Package healthz serves a readiness probe over HTTP for anything that implements transport.Healthzer — one server, or an App aggregating all of its servers.
|
Package healthz serves a readiness probe over HTTP for anything that implements transport.Healthzer — one server, or an App aggregating all of its servers. |
|
http/transcoding
Package transcoding carries the Protobuf half of Forge's HTTP transport: Google HTTP transcoding, ProtoJSON projection, path and query binding, raw HTTP bodies, and stream body fields.
|
Package transcoding carries the Protobuf half of Forge's HTTP transport: Google HTTP transcoding, ProtoJSON projection, path and query binding, raw HTTP bodies, and stream body fields. |
|
message
Package message defines the protocol-neutral contract for asynchronous message transports.
|
Package message defines the protocol-neutral contract for asynchronous message transports. |