Configuration files

A configuration is a document read by two audiences: the person who adjusts it and the program that loads it. STXT serves both with the same file, and with a template it catches in the editor the error that would otherwise show up in production.

Configuration files start small and grow without a plan: keys are added, blocks are copied between environments, comments accumulate explaining why a value is what it is. Over time, the rules that make the file valid —which keys exist, which values they admit, which ones are mandatory— live only in the head of whoever maintains it and in the code that reads it. A misspelled name does not fail on load: it is simply ignored, and the default value comes in silently.

STXT addresses this with three things: a visible hierarchy, which needs neither braces nor ambiguous indentation; text blocks where the long explanation lives next to the value it explains; and a template that turns the implicit rules into a contract the editor applies while typing.

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

	Threads:
		Min: 8
		Max: 64

	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: the 5000 ms
		limit comes from the SLA with the payments provider.

Each group (Network, Threads, Logging) is a node without a value that groups its children; each setting is a node with a value; each feature flag is a node whose value is its name and whose child is its state. The Notes >> block carries what in other formats goes in scattered comments: here it is part of the document, with paragraphs, and a program can display it or ignore it.

The template

The template makes explicit what the program loading the configuration takes for granted:

Template (@stxt.template): com.acme.server
	Description >>
		Server Config: Configuration of a gateway service
		Features: Feature switches, one per node
	Structure >>
		Server Config:
			Name: (1)
			Environment: (1) ENUM [dev, staging, production]
			Network: (1)
				Host: (1)
				Port: (1) NATURAL
				Public url: (?) URL
			Threads: (?)
				Min: (1) NATURAL
				Max: (1) NATURAL
			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

With it, the typical configuration errors stop being silent (closed content model):

  • Ports: 8080 (with an extra s) is CHILD_NOT_DECLARED, not an ignored key that leaves the service listening on the default port.
  • Port: 80a or Enabled: yes are INVALID_VALUE: a natural is a natural and a boolean is true or false.
  • Level: info in lowercase is not INFO: the ENUM compares the value as written.
  • Network is missing, or Host inside Network: TOO_FEW_CHILDREN.

And all of that is visible in the editor, underlined, before the file is saved. In continuous integration, stxt validate config/ performs the same check and stops the deployment of an invalid configuration (The command line).

Several environments, one structure

When the same application is configured for several environments, the temptation is to copy the file and change values. With STXT a single document can be kept with 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
Template (@stxt.template): com.acme.app
	Description >>
		Application Config: Configuration of an application across all its environments
	Structure >>
		Application Config:
			App name: (1)
			Environments: (1)
				Environment: (+)
					Database: (1)
						Url: (1)
						Max connections: (1) NATURAL
					Debug: (1) BOOLEAN

The difference between staging and production is a three-line diff, and an environment missing Database does not validate. If the application prefers one file per environment, the same document is split in three with the same namespace and the same template; the guarantee does not change.

How the program reads it

The program that loads the configuration does not parse text: it receives a tree. With the library of its language it walks the nodes by canonical name —port, max-connections— and obtains values already validated against the template, so the loading code does not need to check types or allowed values: that has already happened. The TypeScript, Java and Python guides show the traversal; template resolution follows the chain of .stxt/ directories of STXT-DISCOVERY-SPEC, the same as in the editor, so validator and program apply the same definition.

Limits

STXT describes the configuration; it does not compute it. There are no variables, no references to other values, no file inclusion, no expressions: a value is the text that is written. It is a design decision (Design principles), in exchange for a configuration file never being able to execute anything or depend on another. Composition —a base file plus one per environment— is done by the application on load, explicitly.

Nor are there default values in the template: a field is mandatory or optional, and if it is optional and missing, the program decides what it is worth.

How this use compares with the formats most often chosen for configuration is developed in STXT vs YAML and STXT vs JSON; the tutorial covers the language itself.