Configuration files

A configuration is read by the person who adjusts it and by the program that loads it. With a template, an error is caught in the editor and not in production.

A service's configuration

Server Config (com.acme.server):
	Name: api-gateway
	Environment: production

	Network:
		Host: 0.0.0.0
		Port: 8080
		Public url: https://api.acme.com

	# The 5000 ms limit comes from the SLA with the payments provider
	Timeouts:
		Read ms: 5000
		Write ms: 5000

	Logging:
		Level: INFO
		Format: json

	Features:
		Feature: experimental-cache
			Enabled: false
		Feature: audit-logging
			Enabled: true

	Notes >>
		Main configuration of the API gateway. Versioned with the application and
		deployed with it.

		Changes to Network and Timeouts go through Reliability review.

In this example we have:

  • Groups: Network, Timeouts, Logging and Features. They are nodes without a value, which group their children.
  • Settings: Host, Port, Level... They are nodes with a value.
  • Feature flags: the value of the Feature node is the name, and its child Enabled is the state.
  • Explanations: a comment for a short note, or a block node such as Notes for a long text. The difference is that Notes is part of the document, and a program can display it.

The template

Template (@stxt.template): com.acme.server
	Structure >>
		Server Config:
			Name: (1)
			Environment: (1) ENUM [dev, staging, production]
			Network: (1)
				Host: (1)
				Port: (1) NATURAL
				Public url: (?) URL
			Timeouts: (?)
				Read ms: (?) NATURAL
				Write ms: (?) NATURAL
			Logging: (?)
				Level: (1) ENUM [DEBUG, INFO, WARN, ERROR]
				Format: (?) ENUM [text, json]
			Features: (?)
				Feature: (*)
					Enabled: (1) BOOLEAN
			Notes: (?) TEXT

A configuration that does not validate

Many configuration loaders ignore a misspelled key and apply the default value. With the template, these errors stop being silent:

# ERROR: this document does not validate
Server Config (com.acme.server):
	Name: api-gateway
	Environment: production
	Network:
		Host: 0.0.0.0
		# `Ports` does not exist, it is `Port`
		Ports: 8080
	Logging:
		# Lowercase `info` is not `INFO`
		Level: info
	Features:
		Feature: audit-logging
			# A BOOLEAN is `true` or `false`
			Enabled: yes

And this is what stxt validate answers:

/home/ana/gateway/config/server.stxt:8: [NODE_NOT_DEFINED_IN_SCHEMA] NOT EXIST NODE ports for namespace com.acme.server (error)
/home/ana/gateway/config/server.stxt:8: [CHILD_NOT_DECLARED] Child 'com.acme.server:ports' not declared in node 'com.acme.server:network' (error)
/home/ana/gateway/config/server.stxt:5: [TOO_FEW_CHILDREN] 0 nodes of 'com.acme.server:port' and min is 1 (error)
/home/ana/gateway/config/server.stxt:11: [INVALID_VALUE] The value 'info' is not one of the allowed values of Level (error)
/home/ana/gateway/config/server.stxt:15: [INVALID_VALUE] Enabled: Invalid boolean (yes) (error)
5 error(s), 0 warning(s)

The VS Code extension marks the same errors while typing. In continuous integration, stxt validate --recursive config/ stops the deployment of an invalid configuration (The command line).

Several environments, one structure

A single document can have one node per environment, and the template guarantees that all of them declare the same things:

Application Config (com.acme.app):
	App name: Billing

	Environments:
		Environment: dev
			Database:
				Url: jdbc:postgresql://localhost/dev
				Max connections: 5
			Debug: true
		Environment: staging
			Database:
				Url: jdbc:postgresql://staging/db
				Max connections: 10
			Debug: false
		Environment: production
			Database:
				Url: jdbc:postgresql://prod/db
				Max connections: 30
			Debug: false

And its template:

Template (@stxt.template): com.acme.app
	Structure >>
		Application Config:
			App name: (1)
			Environments: (1)
				Environment: (+)
					Database: (1)
						Url: (1)
						Max connections: (1) NATURAL
					Debug: (1) BOOLEAN

An environment missing Database does not validate. If one file per environment is preferred, the document is split in three with the same namespace and the same template.

How the program reads it

The program receives an already validated tree. It walks the nodes by their canonical name (port, max-connections) and converts the values. The TypeScript, Java and Python guides show how.

The template is looked up in the .stxt/ directories (STXT-DISCOVERY-SPEC), the same as in the editor. That way, the editor and the program apply the same definition.

Limits

In STXT a value is the text that is written. There are no:

  • Variables or references to other values
  • Inclusion of other files
  • Expressions
  • Default values in the template: if an optional node is missing, the program decides its value

It is a design decision: a configuration file cannot execute anything or depend on another. If a base file has to be composed with one per environment, the application does it on load.

To compare with other configuration formats, see STXT vs YAML, which includes TOML, and STXT vs JSON.