Flatpack is a 12-factor style configuration mechanism for Go programs. It reads data from the process environment and "splats" it into a struct of your choice. You benefit from Go's type safety without writing boilerplate config-loading code; your users never need to touch a config file; you get to spend your time on features that matter.
Populate the environment with some inputs that you want to convey to your app.
export DATABASE_HOST=db1.example.com
export DATABASE_PORT=1234
export WIDGET_FACTORY=jumbo
Next, define your configuration as a plain old Go struct, potentially with nested structs to represent hierarchy.
Ask flatpack to unmarshal the environment into your data structure. If you want flatpack to ignore any of the
fields, mark them with a flatpack:"ignore"
field tag.
import (
"github.com/xeger/flatpack"
)
type Config struct {
Database struct {
Host string
Port int
// Bad idea to mix config with runtime state!
// We're doing it anyway to showcase the ignore tag.
Conn sql.Connection `flatpack:"ignore"`
}
WidgetFactory string
}
func main() {
config = Config{}
err := flatpack.Unmarshal(&config)
}
If Unmarshal returns no errors, your config is available and your app is ready to go!
Flatpack's goals are to:
- Encourage the use of plain old Go data structures to specify configuration and represent it in-memory.
- Decouple the actual reading of config data from validation, propagation across the app, data binding, and other concerns.
- Transparently support various repositories for configuration data, e.g. loading a .env file during development, but reading keys from a Consul or Etcd agent when running in production.
Flatpack uses the reflect
package to recurse through your configuration struct.
As it goes, it maintains a list of field names that it used to arrive at its present
location. This list of field names is transformed into an underscore-delimited string
like so:
Foo.Bar
becomesFOO_BAR
Foo.Bar.BazQuux
becomesFOO_BAR_BAZ_QUUX
(Yes, this means that Foo.BarBaz
and FooBar.Baz
will both be populated from the same
environment variable; don't do that!)
If the environment variable is defined, flatpack parses its value and coerces it to the data type of that field. Supported data types are booleans, numbers, strings, and slices of any of those. If a coercion fails, flatpack returns an error and your app exits with a useful message about what's wrong in the config.
As a coup de grâce, flatpack calls Validate()
on your configuration object
if it defines that method, giving you a chance to validate the finer points of
your configuration or log a startup message with config details.
The flatpack.Getter
interface allows us to load data from sources other than
the environment; it might make sense to build support for HTTP key/value stores
directly into the library.
On the other hand, I like the idea of using the process environment to decouple the producer of config data from the consumer; it produces a naturally-portable app. Tools like envconsul can deal with the k/v store on behalf of my app.