GoConfman's idea is that:
There are multiple sources for configuration (e.g. env vars, files, defaults, etc.). No matter how many sources GoConfMan supports, there are always sources that some developers need but it doesn't support. So, adding a new source must be easy, feels natural, and fits beautifully along with the existing sources.
Besides, control over the order of which we read the sources and fill the configurations must be given to the developer completely, but sensible defaults are nice.
Given these ideas, GoConfMan has a simple structure.
For each source, an interface, named ConfigWithX
, is defined that has a method called BindX()
. Beside it, there is
a function called LoadFromX(interface{})
. Something like this:
package goconfman
type ConfigWithDefaults interface {
BindDefaults()
}
func LoadFromDefaults(config interface{}) {
...
}
And that's it. That's the whole codebase of GoConfMan. A bunch of files like this. You wanna add a new source? Just add a new file like this to your project or be kind and put it in GoConfMan codebase and send a merge request.
You might think that LoadFromX(interface{})
functions are some huge functions that do a lot of stuff and are hard and
time-consuming to write. Actually, the only thing they do is calling their corresponding BindX
method for all the
fields in the given config struct recursively. Of course, there are some differences here and there, but basically
that's the whole point of these functions: to load the config recursively.
And finally, there's a LoadFromAll(interface{})
function that calls all the LoadFromX
functions in a sensible order,
so you can simply use it, or copy and paste it in your own project and change the order.
Now that we know how the whole thing works, let's actually use it.
Just like this
package my_package
import "github.com/Sayed-Soroush-Hashemi/GoConfMan/pkg/goconfman"
type MyConfig struct {
Port int
Workers int
DB DBConfig
}
type DBConfig struct {
IP string
Port int
Username string
Password string
}
func (m *MyConfig) BindDefaults() {
m.Port = 8000
m.Workers = 4
}
func (d *DBConfig) BindDefaults() {
d.IP = "localhost"
d.Port = 5678
d.Username = "admin"
}
func myFunc() {
myConfig := MyConfig{}
goconfman.LoadFromAll(&myConfig)
}
You have a number of config structs which can be composed into each other. You load the whole config with the last two lines in the above code snippet.
As you can see, you write normal code to determine the default values. "Not making things complicated" is one of the
goals of GoConfMan, so the same normal code routine happens in almost all the BindX
methods. For example, here's
BindAliases
methods for the above config structs.
func (d *DBConfig) BindAliases() {
d.Password = d.Username
}
You want more examples? Check out the tests
folder. There are a few X_config.go
files that show you how to use
GoConfMan. You can also check out the load_from_X_test.go
files to see how to use the LoadFromX
functions in your
code.
-
If you have a config struct inside another config struct (like the
DBConfig
in the above code snippet), it must be exposed, otherwise theLoadFromX
functions can't see it and can't call theBindX()
methods on it. -
You can do whatever you want in
BindX
methods. You can set defaults in theBindAliases()
methods and vice versa. But, for the sake of clarity and separation, don't do that.
GoConfman enables you to gradually migrate from your old configuration manager to GoConfMan. You can call LoadFromX
functions on any struct and it recursively looks for GoConfMan-compatible structs and load them. So, you can start small
and just migrate one of your configuration structs and yet you can call LoadFromX
on your top-most config struct and
it works. For an example, check out the non_goconfman_config.go
and load_from_defaults.go
files in the tests
folder.