The Config Is Going To Be Okay

Okconf; Go config library without pretenses

Published

Mirrors

okconf is a super minimalistic configuration library for Go. It supports loading and saving JSON and YAML configurations from and to disk, and that’s really about it.

I could get all philosophical about why I feel that, given the countless existing configuration libraries out there that do so much more, like spf13/viper, I felt the urge to write yet another config library.

I won’t. I just needed a configuration library that didn’t invade my codebase the second I implemented it. I just want to load some values, store them in a struct, and after that I don’t want to deal with the configuration system anymore.

Couldn’t find one I liked, so I wrote a terribly rudimentary implementation myself. It’s named “okconf”, because it really is just OK. It’s not good. It’s just getting by. Sometimes, getting by is all you need.

Design

When designing okconf, I focused on the bare essentials:

  1. The system should have Default Values for when a more specific value isn’t provided through a config
  2. The system should support loading and saving from and to files on disk, and should support complex types and structures
  3. The system should use plain Go language constructs.

Another not-so articulated goal is that I wanted to avoid abstractions wherever possible. They convolute the code, and ideally I’d want to write this thing in around 100 lines total — excluding tests.

Defaults

To keep things simple, the entire config in okconf is defined as a struct. This struct has a Default method, which returns an instance of the struct containing its default values. These are all defined in code, and invoked by okconf in the first step of loading the configuration; essentially creating “layering” for free.

There is a singular interface, aptly named Config that encourages configuration structs to implement Default():

func (e ExampleConfig) Default() Config {
	return ExampleConfig{
		Name:      "Foo",
		StartedAt: time.Now().UTC(),
		Nested: NestedConfig{
			Count: 100,
		},
	}
}

This allows the configuration library to instantiate a struct with the correct values, and afterwards just handing off a pointer to it. Consumers of the configuration can just deal with the struct and its values, rather than with okconf mechanisms.

Saving and Loading

I still think Go generics are hugely weird, but they did come in handy when writing a Load function that deserializes into an instance of our config struct:

cfg, err := okconf.LoadJSON[ExampleConfig]("/path/to/file")

To make things better, this is the only touchpoint okconf has with your code, apart from the Default() function mentioned in the previous section. Loading configuration is a single function call, as is saving it afterwards.

That’s all there’s to it.

The code is ridiculously simple, and so are the features. If you have any complex configuration requirements, like merging environment variables or command line arguments, you’ll probably outgrow this library quickly. But until then, it’s ok.