Quickstart

In about 2 minutes you will build configs for two services in two environments, then make three everyday changes that would normally mean editing files across services by hand, and see a broken reference stop the build.


1. Install

brew tap microconfig/microconfig && brew install microconfig

No Homebrew? Get a binary for macOS, Linux, Windows or a Java jar.

2. Get the example

git clone https://github.com/microconfig/microconfig-quickstart.git && cd microconfig-quickstart

Or download the zip and unzip it.

The example has two services, payment-backend and payment-frontend, in the payments group, and two environments, dev and prod. Shared parts live in components/common:


                components
                ├── common
                │   ├── log           # logging settings and the logback.xml template
                │   ├── monitoring    # monitoring settings for every service
                │   └── postgres      # database settings, dev and prod hosts
                └── payments
                    ├── payment-backend
                    └── payment-frontend
                envs
                ├── base.yaml         # which services exist
                ├── dev.yaml
                └── prod.yaml
            

3. Build

microconfig -e dev

                Filtered 2 component(s) in [dev] env.
                Copied 'payment-backend' template ../log/logback.xml -> logback.xml
                Generated payment-backend/application.yaml
                Generated payment-backend/deploy.yaml
                Copied 'payment-frontend' template ../log/logback.xml -> logback.xml
                Generated payment-frontend/application.yaml
                Generated payment-frontend/deploy.yaml
                Generated [dev] configs in 8ms
            

Open build/payment-backend/application.yaml. Monitoring, database and logging settings are already merged in from common, and the database host is the one for dev. These are plain files your services can read as is.

For comparison: written by hand, the same configs for two services in two environments take 92 lines (see initial.md). The Microconfig sources take 50, and every new service adds only the lines that are unique to it.

Now the interesting part

Four situations from real life. Each one takes a single edit.

1Change a shared part once

Ops wants a new metrics endpoint on every service.

Edit components/common/monitoring/application.yaml:


                    monitoring:
                      base-path: /monitoring
                      endpoints: info, health, ready, prometheus, metrics
                

Build again. Microconfig compares the result with the previous build and writes a diff next to each service's configs:

microconfig -e dev && tail -n +1 build/*/diff-application.yaml

                    ==> build/payment-backend/diff-application.yaml <==
                    monitoring:
                      endpoints: info, health, ready, prometheus -> info, health, ready, prometheus, metrics

                    ==> build/payment-frontend/diff-application.yaml <==
                    monitoring:
                      endpoints: info, health, ready, prometheus -> info, health, ready, prometheus, metrics
                

One edit, every service that includes monitoring is updated, and the diff shows exactly what will change before you deploy.

2Change a value other services depend on

The backend moves its API to /api/v2. Last time, the frontend found out in production.

Edit components/payments/payment-backend/application.yaml:


                    server:
                      port: 80
                      context: /api/v2
                

The frontend doesn't copy this value, it references it: path: ${payment-backend@server.context}. Build and check the diff:

microconfig -e dev && tail -n +1 build/*/diff-application.yaml

                    ==> build/payment-backend/diff-application.yaml <==
                    server:
                      context: /api -> /api/v2

                    ==> build/payment-frontend/diff-application.yaml <==
                    payment-backend:
                      path: /api -> /api/v2
                

You changed the backend, and the frontend followed. No one had to remember that they are connected.

3Add an environment in two small files

A client needs their own stage by tomorrow, with its own database.

Create envs/staging.yaml with the same services as the base environment:


                    include:
                      env: base
                

And override only what differs. Create components/common/postgres/application.staging.yaml:


                    #var dbHost: 30.30.30.30
                

Build the new environment:

microconfig -e staging && grep url build/payment-backend/application.yaml

                    url: jdbc:postgres://30.30.30.30:5432/payments
                

A complete new environment for every service: the new database host, and everything else inherited from the base config.

4Break a reference on purpose

Someone renames a key or makes a typo. Without Microconfig, the service finds out at startup, in production.

In components/payments/payment-frontend/application.yaml, misspell the reference:


                    payment-backend:
                      path: ${payment-backend@server.contxt}
                
microconfig -e dev

                    Can't build configs for root component 'payment-frontend[dev]'.
                    Exception in
                        ../components/payments/payment-frontend/application.yaml:10'
                        payment-backend.path=${payment-backend@server.contxt}
                    Can't resolve ${payment-backend@server.contxt}
                

The build stops with a non-zero exit code and points to the file and line. In CI, that means the broken config never reaches an environment.

Useful build options

  • -e prod build all services of an environment
  • -g payments build only a group of services
  • -s payment-backend build only specific services
  • -r path/to/configs config root, the current directory by default
  • -d path/to/output where to put the result, ./build by default

All options are in the documentation.

Try it on your own configs

Pick one block that is copy-pasted between your services, like monitoring, logging or a database pool. Move it to components/common, #include it, and build. Your other configs can stay as they are until you are ready.

Next steps: