Configuration
It’s a common practice for microservices to define properties whose values can be configured without code changes, often by operators. Connection strings to a database, number of rows to display in a table, a timeout value, are all examples of configuration properties.
In Microbus, the microservice owns the definition of its config properties, while the configurator core microservice owns their values. The former means that microservices are self-descriptive and independent, which is important in a distributed development environment. The latter means that managing configuration values and pushing them to a live system are centrally controlled, which is important because configs can contain secrets and because pushing the wrong config values can destabilize a deployment.
Definition
Configuration properties must first be defined by the microservice using DefineConfig. In the following example, the property is named Foo and given the default value Bar and a regexp requirement that the value is comprised of at least one letter and only letters. The case-sensitive property name is required. Various options allow setting a default value, enforcing validation, and more.
con.New("www.example.com")
con.DefineConfig("Foo", cfg.DefaultValue("Bar"), cfg.Validation("str ^[A-Za-z]+$"))Initial Values
The initial value of any configuration property is its default value.
On startup, the microservice looks for config.yaml or config.local.yaml files in the current working directory and its ancestor directories. Precedence is given to YAML files in a subdirectory over its ancestor directory. Within a given directory, config.local.yaml takes precedence over config.yaml. In the TESTING deployment environment these files are not read — a microservice under test sees only its declared defaults plus values set explicitly in test code, as the notes below detail.
A property value is applicable to a microservice that defines a property with the same name, and whose hostname equals or is a sub-domain of the domain name. The special domain all applies to all microservices.
www.example.com:
Foo: Baz
example.com:
Foo: Bam
com:
Foo: Bax
all:
Foo: BatFetching Values from the Configurator
On startup, the microservice contacts the configurator microservice to ask for the values of its configuration properties. If an override value is available at the configurator, it is set as the new value of the config property; otherwise, the default value of the config property is set instead.
Configs are accessed using the Config method of the Connector.
foo := con.Config("Foo")The microservice keeps listening for a command on the control subscription :888/config-refresh and will respond by refetching config values from the configurator. The configurator issues this command on startup and on a periodic basis (every 20 minutes) to ensure that all microservices always have the latest config. If new values are received by the microservice, they will be set appropriately and the OnConfigChanged callback will be invoked.
con.SetOnConfigChanged(func (ctx context.Context, changed func(string) bool) error {
if changed("Foo") {
con.LogInfo(ctx, "Foo changed",
"to", con.Config("Foo"),
)
}
return nil
})More commonly, the configuration property is defined by the coding agent who takes care of generating the boilerplate code and creating an accessor method named after the configuration property, e.g. svc.Foo(), and the skeleton code of the OnChangedFoo callback.
Notes
In the TESTING deployment environment a microservice reads neither config.yaml / config.local.yaml files nor the configurator. It starts from its declared default values and sees only the config a test sets explicitly in code. This keeps tests hermetic — a stray config.yaml in an ancestor directory can’t leak into a test run, and a test declares the exact config surface it depends on.
Set test-relevant config values programmatically before the app starts, using the generated typed setter for the property (e.g. svc.SetFoo("bar")) or the generic SetConfig:
svc := myservice.NewService()
svc.SetConfig("Foo", "bar") // or the typed svc.SetFoo("bar")
app := application.New()
app.Add(svc, tester)
app.RunInTest(t)Outside TESTING, programmatically-set values are overridden on the next fetch from the configurator, so limit SetConfig to testing scenarios where configurator fetching is disabled.