configurer

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: May 12, 2024 License: MIT Imports: 10 Imported by: 0

README

Configurer

Configuration handling for go server applications

This package maintains configuration file reads and updates across a long-running go application.

Usage

Implement the following interfaces to your liking:

type Config struct {
	ThisEntry string `json:"this_entry"`
	ThatEntry int    `json:"that_entry"`
}

func defaultConfig() *Config {
	return &Config{
		ThisEntry: "foo",
		ThatEntry: "baz",
	}
}

type Loader struct {
	filename string
}

func NewLoader(string filename) *Loader {
	return &Loader{filename: filename}
}

func (loader *Loader) Filename() string {
	return loader.filename
}

func (loader *Loader) Load() (*Config, error) {
	contents, err := os.ReadFile(loader.filename)
	if err != nil {
		return nil, fmt.Erorf("reading config file: %w", err)
	}

	conf := defaultConfig()
	err = json.Unmarshal(contents, conf)
	if err != nil {
		return nil, fmt.Errorf("parsing config file: %w", err)
	}

	return conf, nil
}

To read config:

func runServer() error {
	ctx := context.Background()

	loader, err := NewLoader("config.json")
	if err != nil {
		return err
	}

	cctrl, err := configurer.New(loader, slog.Default())
	if err != nil {
		return err
	}
	...
}

Then, configuration can be read from cctrl.Config() directly. However, portions of confuguration handling can be spread across different subsystems via Notifier:

func runServer() error {
	...
	// set up services
	someService := NewSomeservice(ctx)
	otherService := NewOtherservice(ctx)

	notifier := configurer.NewNotify(ctx, cctrl, slog.Default())

	notifier.RegisterServices(someService, otherService)
	notifier.RegisterAborters(someService)

	// sends initial configuration notifications to services
	if err := notifier.Notify(); err != nil {
		return fmt.Errorf("configuration error: %w", err)
	}

	if watch {
		// starts file watcher on configuration file
		notifier.Watch()
	}

	return someService.Run(otherService)
}

To consume config at services:

...
func (svc *someService) UpdateConfig(ctx context.Context, ctrl *configurer.Control) error {
	cfg, ok := ctrl.Config().(*Config)
	if !ok {
		return errors.New("unknown configuration type")
	}

	if ctrl.IsChanged("ThisEntry") {
		...
	}

	return nil
}

To implement aborter:

func (svc *someService) Abort(err error) {
	svc.logger.Warn("someService initiates shutdown due to error", "error", err)
	svc.Server.Shutdown()
}

Licensing

SPDX-License-Identifier: BlueOak-1.0.0 OR MIT

This software is licensed under two licenses of your choice: Blue Oak Public License 1.0, or MIT Public License.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrNoConfigFile = errors.New("no configuration file found")

Functions

This section is empty.

Types

type Aborter

type Aborter interface {
	Abort(error)
}

Aborter provides abort notification to spread.

type ConfigLoader

type ConfigLoader interface {
	// Filename returns the file name which can then be watched by fsnotify.
	Filename() string
	// Load provides a new Configuration from the filename on demand. Otherwise,
	// it should produce an error.
	Load() (Configuration, error)
}

ConfigLoader can load configuration on demand.

Notifier relies on the fact the configuration file is coming from a file in the operating system, and it uses fsnotify package to watch the file system.

type Configuration

type Configuration any

Configuration holds applications' configuration.

It can be anything, as long as diff3 can handle it

type Control

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

Control keeps track of the current and previous configurations, and a changelog.

func New

func New(loader ConfigLoader, logger *slog.Logger) (*Control, error)

New returns a new Control object, or an error.

It needs a ConfigLoader for reading the configuration, and a logger for logging purposes. A new configuration is read immediately.

func (*Control) Config

func (ctrl *Control) Config() Configuration

Config returns the current configuration. It needs to be casted to the final type.

func (*Control) IsChanged

func (ctrl *Control) IsChanged(item string) bool

IsChanged confirms whether a certain portion of the struct has been changed.

Item name is the configuration key's name. Deep configurations use "." as a delimiter. Numbers represent keys of slices.

"*" can be used at the end of the string as a wildcard for any keys. For example, if "Database.Connection.Host" changes, the following wildcards will match: "*", "Database.*", "Database.Connection.*". Note: "*" matches full key, there's no substring matching.

type Notifier

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

Notifier tells subsystems about configuration changes.

func NewNotifier

func NewNotifier(ctx context.Context, ctrl *Control, logger *slog.Logger) *Notifier

NewNotifier returns a new notifier object.

func (*Notifier) Notify

func (notif *Notifier) Notify() error

Notify sends configuration change notification to Updateable services.

This method should be called right after services and aborters registered. It sends notification to the configuration object itself too, if it implements Updateable.

func (*Notifier) RegisterAborters

func (notif *Notifier) RegisterAborters(svc ...Aborter)

RegisterAborters adds Aborter services to the list of services handling abortions.

func (*Notifier) RegisterServices

func (notif *Notifier) RegisterServices(svc ...Updateable)

RegisterServices adds Updateable services to the list of services to be notified.

func (*Notifier) Watch

func (notif *Notifier) Watch() error

Watch starts configuration file watching for changes using fsnotify.

It handles modify and remove events. On removal, it tries to re-add the file to the watchlist immediately, and continues trying with an exponential backoff (starting with 1/2 seconds, with a multiplier of 1.5, backing off after 10 tries).

type Updateable

type Updateable interface {
	// UpdateConfig is called by Notifier on configuration change. When a change
	// cannot be handled for some reason, the error initiates abort.
	UpdateConfig(context.Context, *Control) error
}

Updateable enables objects to have their configurations updated.

Jump to

Keyboard shortcuts

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