Giter Club home page Giter Club logo

go-figure's Introduction

go-figure

An environment configuration bus with both synchronous and asynchronous implementations.

Getting started

Add github.com/Matthewacon/go-figure v0.0.3 to the require section in your go.mod.

Setup

Somewhere in your application, define an environment type that fits your needs and implement config.IEnvironment:

package environment

import (
 "github.com/Matthewacon/go-figure"
 "github.com/Matthewacon/go-figure/config"
)

//Define your environment type and implement config.IEnvironment
type Environment struct {
 name string
 config.IConfigBus
}
//config.IEnvironment
func (env *Environment) SetConfig(cfg config.IConfigBus) { env.IConfigBus = cfg }
func (env *Environment) GetConfig() config.IConfigBus    { return env.IConfigBus }
func (env *Environment) String() string                  { return env.name }

//Declare your environments
var (
 ALPHA,
 BETA,
 STAGING,
 PRODUCTION =
 Environment{ name: "ALPHA" },
 Environment{ name: "BETA" },
 Environment{ name: "STAGING" },
 Environment{ name: "PRODUCTION" }
)

//Optionally initialize all of your environments beforehand
func init() {
 for _, env := range []config.IEnvironment{ALPHA, BETA, STAGING, PRODUCTION} {
  go_figure.NewSynchronousConfig(env)
 }
}

Then at your application entry point, choose the environment

package main

import (
 "internal/environments"
 "github.com/Matthewacon/go-figure"
)

func main() {
 //Select your environment and initialize your config bus
 env := environments.ALPHA
 go_figure.NewSynchronousConfig(env)
 //You're now all set to start adding configuration parameters and
 //parameter access listeners!
}

Adding and retrieving configuration parameters

Your configuration requirements may vary depending on the usage within your application. To make the most of your configuration options, you may define multiple parameter key and value types.

package "sql"

import "github.com/Matthewacon/go-figure/config"

type SqlConfigKey string
//config.IParameterKey
func (k *SqlConfigKey) Key() interface{} { return k }
func (k *SqlConfigKey) String() string { return string(*k) }

type SqlConfigValue int
//config.IParameterValue
func (v *SqlConfigValue) Value() interface{} { return v }
func (v *SqlConfigValue) String() string { return fmt.Sprintf("%d", *v) }

//Sql configuration options
var (
 CONNECTION_TIMEOUT,
 MAX_CONNECTIONS,
 MAX_IDLE_TIME SqlConfigKey =
 "some key identifier",
 "that matches the type",
 "of your custom key"
)

func NewSqlComponent(env config.IEnvironment) {
 cfg := env.GetConfig()
 var timeout SqlConfigValue
 var ok bool
 if timeout, ok = cfg.GetParameter(CONNECTION_TIMEOUT); !ok {
  timeout = SqlConfigValue(32)
  cfg.SetParameter(CONNECTION_TIMEOUT, timeout)
 }
}

Adding configuration parameter listeners

You may want to listen for configuration changes to make live updates to your environment. There are two main events that you can listen to: PARAMETER_ACCESS_READ and PARAMETER_ACCESS_WRITE. If you want to handle both events, you may also use PARAMETER_ACCESS_ANY.

package sql

import (
 "fmt"

 "github.com/Matthewacon/go-figure/config"
)

func NewSqlComponent(env config.IEnvironment) {
 cfg := env.GetConfig()
 var timeout SqlConfigValue
 var ok bool
 if timeout, ok = cfg.GetParameter(CONNECTION_TIMEOUT); !ok {
  timeout = SqlConfigValue(32)
  cfg.SetParameter(CONNECTION_TIMEOUT, timeout)
 }
 //listen for any changes to CONNECTION_TIMEOUT
 cfg.AddParameterListener(
  CONNECTION_TIMEOUT,
  config.PARAMETER_ACCESS_WRITE,
  func(prev config.IParameterValue, access config.ParameterAccess) error {
   //update something here
   newValue, ok := cfg.GetParameter(CONNECTION_TIMEOUT)
   if !ok {
    //entry was deleted, do something about it
   }
   timeout = newValue
   return nil
  },
 )
}

Running the tests and benchmarks

Tests:

go test ./...

Benchmarks:

go test --bench=. ./...

Performance

BenchmarkConfigKVInsertionPrebuilt-8                   	12965660	       91.6 ns/op
BenchmarkConfigKVInsertion-8                           	3994064	      506 ns/op
BenchmarkConfigKVLookup-8                              	1887486	      812 ns/op
BenchmarkConfigKVLookupPrebuilt-8                      	18696658	       64.3 ns/op
BenchmarkConfigKVRemoval-8                             	1426365	      755 ns/op
BenchmarkReadAccessCallback-8                          	12074638	       98.8 ns/op
BenchmarkWriteAccessCallback-8                         	8582522	      140 ns/op
BenchmarkAnyAccessCallbackOnRead-8                     	12217536	       97.6 ns/op
BenchmarkAnyAccessCallbackOnWrite-8                    	8502228	      141 ns/op
BenchmarkAnyAccessCallbackOnReadVerticallyScaled-8     	1000000	     1909 ns/op
BenchmarkAnyAccessCallbackOnReadHorizontallyScaled-8   	  34530	   119802 ns/op

License

This project is licensed under the M.I.T. License.

go-figure's People

Contributors

matthewacon avatar

Watchers

James Cloos avatar  avatar

go-figure's Issues

Add type checks on value updates

If a kv pair is inserted, and one previously existed with the same key, perform type checks to ensure that the type of the new value matches that of the previous value.

Add support for delayed listener changes

When reconfiguring a service, multiple config kv pairs may change within a short span of time. some cases it would make sense to apply all of the service configuration changes as a set, rather than restarting the service for every change. Add an API for listening on a set of configuration key changes, and once all of those changes have been fulfilled, running the provided handler.

Change sets should also be reversible, in the event that there is an error applying any of the changes in the set.

Blocked by #8.

Implement both IParameterKey and IParameterValue on all basic types

Similar to #3, but for config parameters. Once everything is implemented, something as simple as a config.Key(k interface{}) and config.Value(v interface{}) functions should be added.

package config

func invalid(fn string, i interface{}) {
 panic(fmt.Errorf(
  "Invalid key type: %s\n", 
  fn,
  reflect.TypeOf(i).Name(),
 ))
}

func Key(k interface{}) IParameterKey {
 switch k.(type) {
  int: return IntKey(k.(int))
  /*...*/
  default: invalid("Key", i)
 }
}

func Value(v interface{}) IParmaterValue {/*...*/}

Config contexts for access listeners

Simplify interactions between the config bus and parameter access listeners. Right now a simple listener implementation looks something like:

package main

import "github.com/Matthewacon/go-figure/config"

func updateSomeParameter(value config.IParameterValue) {
 /*...*/
}

func main() {
 cfg := /*...*/
 cfg.AddParameterListener(
  key,
  config.PARAMETER_READ_ACCESS,
  func(prev config.IParameterValue, access config.ParameterAccess) error {
   newValue, ok := cfg.GetParameter(key)
   if !ok {
    cfg.SetParameter(key, prev)
    newValue = prev
   }
   updateSomeParameter(newValue)
   return nil
  },
 )
}

Rather than having to capture both the config and desired key, simply pass off a config context conformed to an interface that provides easy access to the parameter key, updated value and access type. Something like:

type IListenerContext interface {
 //with respect to the key that the listener was registered with
 Key() IParmeterKey
 Value() (IParameterValue, bool)
 ValueOr(or IParameterValue) IParameterValue
 SetValue(value IParameterValue)
 
 Config() IConfigBus
 AccessType() ParameterAccess
}

Then the new ParameterListener prototype would be something like:

type ParameterListener func(context IListenerContext, prev IParameterValue)

And listener implementations would be much simpler:

package main

import "github.com/Matthewacon/go-figure/config"

func updateSomeParameter(value config.IParameterValue) {
 /*...*/
}

func main() {
 cfg := /*...*/
 cfg.AddParameterListener(
  key,
  config.PARAMETER_READ_ACCESS,
  func(context config.IListenerContext, prev config.IParameterValue) error {
   updateSomeParameter(context.ValueOr(prev))
   return nil
  },
 )
}

With this change, handlers can independent of their setup context and thus reused for multiple keys, if desired:

package main

import "github.com/Matthewacon/go-figure/config"

func notifyValueChange(key config.IParameterKey, value config.IParameterValue) {
 /*...*/
}

func main() {
 cfg := /*...*/
 listener := func(context config.IListenerContext, prev config.IParameterValue) error {
  notifyValueChange(context.Key(), context.ValueOr(nil))
  return nil
 }
 cfg.AddParameterListener(
  key1,
  config.PARAMETER_WRITE_ACCESS,
  listener,
 )
 cfg.AddParameterListener(
  key2,
  config.PARAMETER_WRITE_ACCESS,
  listener,
 )
}

Optional config value retrieval

Something like:

type IConfigBus interface {
 /*...*/
 GetParameterOr(key IParameterKey, or IParameterValue) IParameterValue
 /*...*/
}

Queries to non-existent KV pairs would write the new pair to the config bus.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.