anchor

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 30, 2025 License: MIT Imports: 14 Imported by: 1

README

anchor

Build Status Report Card Go Reference codecov

Library to manage component lifetime in a Go microservice architecture.

Features

  • Run Components in parallel using Go Routines
  • Simple API to manage component lifetime
  • Graceful shutdown of components
  • Freedom of choise for dependency injection
  • Convenience methods to wire external APIs into Anchor

Quickstart

cmd/main.go

package main

import (
    "github.com/kyuff/anchor"
    "example.com/myapp/internal/app""
)

func main() {
  os.Exit(app.Run(anchor.DefaultSignalWire()))
}

internal/app/anchor.go

package app

import (
    "github.com/kyuff/anchor"
)

func Run(wire anchor.Wire) int {
  return anchor.New(wire, anchor.WithDefaultSlog().
       Add(
         anchor.Close("database.Connection", func() io.Closer {
            return database()
         }),
         NewHttpServer()
       ).
       Run()
}

internal/app/database.go

package app

import (
    "database/sql"
    "os"
)

var database = anchor.Singleton(func() (*sql.DB, error) {
  return sql.Open("postgres", os.Getenv("DATABASE_URL")
})

internal/app/http.go

package app

import (
    "context"
    "net/http"
    "os"

    "example.com/myapp/internal/api"
)

type HttpServer struct {
    server *http.Server
}

func NewHttpServer() *HttpServer {
    return &HttpServer{
        server: &http.Server{
            Addr: os.Getenv("HTTP_ADDR"),
        },
    }
}

func (h *HttpServer) Setup(ctx context.Context) error {
    return api.Register(http.DefaultServeMux, database())
}

func (h *HttpServer) Start(ctx context.Context) error {
    return h.server.ListenAndServe()
}

func (h *HttpServer) Close(ctx context.Context) error {
    return h.server.Shutdown(ctx)
}

Documentation

Index

Examples

Constants

View Source
const (
	// OK signals the Anchor shutdown with no errors
	// after it was interrupted by the Wire
	OK = 0
	// Interrupted signals the Anchor failed to set up or close within the timeout
	Interrupted = 1
	// SetupFailed signals the Anchor received an error during Setup.
	SetupFailed = 3
	// Internal signals the Anchor shutdown due to a Component returning an error.
	Internal = 4
)

Variables

This section is empty.

Functions

func Singleton

func Singleton[T any](fn func() (T, error)) func() T

Singleton creates a func that returns a value T created by fn. The value is created only once. If fn returns an error, Singleton panics.

This function is meant to be used as part of application wiring, where a panic seems may seem acceptable.

It is similar to sync.OnceValue, but differs in the way that the creator function fn can return an error.

Example
type Service struct {
	ID       int
	HostName string
}

service := anchor.Singleton(func() (*Service, error) {
	fmt.Println("Creating service")
	return &Service{
		ID:       rand.Int(),
		HostName: "localhost",
	}, nil
})

s := service()

fmt.Printf("host: %s\n", s.HostName)
fmt.Printf("id equal: %v\n", service().ID == s.ID)
Output:

Creating service
host: localhost
id equal: true

Types

type Anchor

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

Anchor the application Components to a Wire.

The Anchor will run each Component in the order they are added. A Component will follow the lifecycle steps (Setup, Start, Close), so the full application start and close gracefully.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/kyuff/anchor"
)

type ExampleService struct {
	name string
}

func (svc *ExampleService) Setup(ctx context.Context) error {
	fmt.Printf("Setup of %s\n", svc.name)
	return nil
}

func (svc *ExampleService) Start(ctx context.Context) error {
	fmt.Printf("Starting component\n")
	// A component can act in three different ways:
	// 1. Block using the ctx as a signal for when to gracefully shut down
	// 2. Block and implement the Close() method as a signal for graceful shutdown
	// 3. Return a nil when there is no background work that needs to be done.
	<-ctx.Done()
	return nil
}

func (svc *ExampleService) Close() error {
	fmt.Printf("Closing %s\n", svc.name)
	return nil
}

func main() {

	code := anchor.New(newAutoClosingWire(time.Millisecond*100)).
		Add(
			&ExampleService{name: "component-a"},
			&ExampleService{name: "component-b"},
		).
		Run()

	fmt.Printf("Exit code: %d\n", code)

}

func newAutoClosingWire(duration time.Duration) anchor.WireFunc {
	return func(ctx context.Context) (context.Context, context.CancelFunc) {
		ctx, cancel := context.WithCancel(ctx)
		go func() {
			defer cancel()
			time.Sleep(duration)
			fmt.Printf("Closing down the Anchor\n")
		}()
		return ctx, cancel
	}
}
Output:

Setup of component-a
Setup of component-b
Starting component
Starting component
Closing down the Anchor
Closing component-b
Closing component-a
Exit code: 0

func New

func New(wire Wire, opts ...Option) *Anchor

New creates an Anchor that is bound to the Wire. When the Wire is closed, the Anchor will Close all Components.

func (*Anchor) Add

func (a *Anchor) Add(components ...Component) *Anchor

Add will manage the Component list by the Anchor.

When Run is called, all Components will be started in the order they were given to the Anchor.

func (*Anchor) Run

func (a *Anchor) Run() int

Run is blocking until the Wire closes or a Component returns an error. When either happens, each Component is closed in in reverse order of which they were added and started.

type Component

type Component interface {
	Start(ctx context.Context) error
}

Component is a central part of an application that needs to have it's lifetime managed.

A Component can optionally Setup and Close methods.. By doing so, Anchor will guarantee to call the methods in order: Setup, Start, Close. This allows applications to prepare and gracefully clean up.

An example of a Component could be a database connection. It can read it's configuration in the Setup phase, connect at Start and disconnect at Close. If the database connection disconnects due to a network outage, it can return and error from Start, which closes down the entire application.

func Close

func Close(name string, closer io.Closer) Component

Close creates a component that have an empty Start() and Setup() method, but have Close. It is a convenience to run cleanup code

func Make

func Make[T Component](name string, setup func() (T, error)) Component

Make a component by it's setup func. A convenience when the Component is not needed as a reference by other parts of the application, but just needs it's lifecycle handled.

func MakeProbe

func MakeProbe[T Component](name string, setup func() (T, error), probe func(ctx context.Context) error) Component

MakeProbe a component by it's setup and probe functions. A convenience when the Component is not needed as a reference by other parts of the application, but just needs it's lifecycle handled. The probe function is called after Start to check if the Component is ready.

func Setup

func Setup(name string, fn func() error) Component

Setup creates a component that have an empty Start() and Close() method, but have Setup. It is a convenience to run code before full application start.

type Logger

type Logger interface {
	// InfofCtx logs on an INFO level
	InfofCtx(ctx context.Context, template string, args ...any)
	// ErrorfCtx logs on an ERROR level
	ErrorfCtx(ctx context.Context, template string, args ...any)
}

Logger is an interface for logging by the Anchor. There is default implementation for slog.Logger.

type Option

type Option func(cfg *config)

Option for an Anchor configuration.

See config.go for defaults.

func WithAnchorContext

func WithAnchorContext(ctx context.Context) Option

WithAnchorContext runs the Anchor in the given Context. If it is canceled, the Anchor will shutdown.

Default: context.Background()

func WithCloseTimeout

func WithCloseTimeout(timeout time.Duration) Option

WithCloseTimeout is the combined time components have to perform a graceful shutdown.

Default: No timeout

func WithDefaultSlog

func WithDefaultSlog() Option

WithDefaultSlog uses slog.Default() for logging

func WithExponentialReadyCheckBackoff

func WithExponentialReadyCheckBackoff(base time.Duration) Option

WithExponentialReadyCheckBackoff doubles the wait time with each retry.

func WithFixedReadyCheckBackoff

func WithFixedReadyCheckBackoff(d time.Duration) Option

WithFixedReadyCheckBackoff waits a fixed amount of time between retries.

func WithLinearReadyCheckBackoff

func WithLinearReadyCheckBackoff(increment time.Duration) Option

WithLinearReadyCheckBackoff increases the wait time linearly with each retry.

func WithLogger

func WithLogger(logger Logger) Option

WithLogger sets the Logger for the application.

Default: No logging is done.

func WithNoopLogger

func WithNoopLogger() Option

WithNoopLogger disables logging

func WithReadyCallback

func WithReadyCallback(fn func(ctx context.Context) error) Option

WithReadyCallback is called when all Components have been Setup and Started and succeeded any Probe.

This is useful for enabling features that require all Components to be ready before they can be used. If the function returns an error, the Anchor will fail to start and go into a shutdown state.

func WithReadyCheckBackoff

func WithReadyCheckBackoff(fn func(ctx context.Context, attempt int) (time.Duration, error)) Option

WithReadyCheckBackoff is called when a Component fails a Probe.

The function should return the amount of time to wait before retrying. If the function returns an error, the Anchor will fail to start and go into a shutdown state.

Default: linear backoff with 100 millisecond increment

func WithSetupTimeout

func WithSetupTimeout(timeout time.Duration) Option

WithSetupTimeout fails an Anchor if all Components have not been Setup within the timeout provided.

Default: No timeout

func WithSlog

func WithSlog(log *slog.Logger) Option

WithSlog uses the give *slog.Logger

func WithStartTimeout

func WithStartTimeout(timeout time.Duration) Option

WithStartTimeout fails an Anchor if all Components have not been Started within the timeout provided.

Default: No timeout

type TestingM

type TestingM interface {
	Run() int
}

type Wire

type Wire interface {
	Wire(ctx context.Context) (context.Context, context.CancelFunc)
}

Wire keeps the Anchor running.

When the returned Context is canceled, all Components are Closed and the CancelFunc is called.

func DefaultSignalWire

func DefaultSignalWire() Wire

DefaultSignalWire returns a Wire that listens for SIGINT and SIGTERM.

func SignalWire

func SignalWire(sig os.Signal, sigs ...os.Signal) Wire

SignalWire returns a Wire that listens for the given os signals.

Use DefaultSignalWire to listen for SIGINT and SIGTERM.

func TestingWire

func TestingWire(m TestingM) Wire

TestingWire returns a Wire for use in testing. It will Run the tests and and then signal the application to shutdown.

type WireFunc

type WireFunc func(ctx context.Context) (context.Context, context.CancelFunc)

WireFunc is a convenience type for creating a Wire from a function.

func (WireFunc) Wire

Wire calls WireFunc.

Directories

Path Synopsis
Package env is used to set environment variables, primarily during a test run.
Package env is used to set environment variables, primarily during a test run.
internal