Write code to control your application. Configure the code’s behavior using standard configuration files. After that, go full circle: write code to control the code that runs your application. In this blog post I’ll explain when you might want do that.

Software ideas tend to start out as hard-coded prototypes. As those prototypes are built upon (don’t do this) or are redeveloped to full-blown applications, the hard-coding is supplanted by configuration files.

Most big applications we take for granted are implemented this way. I’m even willing to proclaim that most big applications got big because they were implemented this way - their configurability gave them a long-term edge over less-configurable competitors. Configurability, therefore, is an important part of any aspiring application’s architecture. Consequently, choosing the right format for your configuration data is important.

For most applications, I personally use very standard configuration file formats (e.g. .json, .xml, .ini). However, on my current project—PlateyPlatey—I’m getting very close to going, what I call, “full circle” with my configuration files by writing them in a scripting language. This might seem extreme, but allow me to give an example of how a typical project would, if it got popular enough, go down that path:

Make an application that prints labels. The application must prompt the user to type the label’s text into a textbox. After the user has finished typing, they will press the “print” button, which will print the label.

The (javascript-style) pseudocode this app would look something like this:

printButton.onClick(() => {
	const text = textBox.value;
	print({ content: text, fontSize: 14, fontFamily: Arial });	
});

Easy, but surely most developers know what’s coming next:

That label software is neat; however, some users would really like to be able to control the size and font-family of all the text that appears on the label. This does not need to be integrated into the UI.

Configuration files come to the rescue:

{ "fontSize": "14pt", "fontFamily": "Arial" }
printButton.onClick(() => {
	const text = textBox.value;
	print({ content: text, fontSize: config.fontSize, fontFamily: config.fontFamily});
});

As the software gets more popular, special cases begin to creep in:

The label software is great - a lot of people are using it now. However, a new user, George, mentioned that he’d like an “Are you sure you want to print?” confirmation to appear when he clicks print.

Configuration files also come to the rescue:

{
	"showAreYouSureYouWantToPrintDialog": true,
	"areYouSureYouWantToPrintText": "Are you sure you want to print?",
	"fontSize": "14pt",
	"fontFamily": "Arial"
}
function print(text) {
	print({ content: text, fontSize: config.fontSize, fontFamily: config.fontFamily });
}

printButton.onClick(() => {
	const text = textBox.value;

	if(config.showAreYouSureYouWantToPrintDialog) {
		if(confirm(config.areYouSureYouWantToPrintText)) {
			print(text);
		} else return;
	}
	else print(text);
});

Notice, though, that I’ve had to change the code behind my print button to facilitate this special request. Special cases are the bane of all software developers. As any software gets more popular, they start to pop up alot more frequently. Integrating each one into the codebase is asking for trouble.

If reconfiguring parts of the application is going to happen often, then the next step is to provide configuration options for adding extra behaviors. The logical next step would be to implement something like this:

{
	"extraBehaviors": [{
		"when": "print-button-clicked",
		"do": "show-a-confirmation-dialog",
		"associatedData": { "confirmationText": "Are you sure you want to print?" }
	}]
}

However, it’s difficult to capture the essence of how the application should behave in such a configuration format. Do I just want to show a confirmation dialog or do I want the print operation to stop if they clicked no in the dialog? As the software grows in popularity, there will almost certainly be a special case that requires either behavior.

What we really need to capture is new control flow and logic in the configuration. Hm, I wonder if we know of a data format that lets us describe flow an logic?. Wait—yes—we do: programming languages! The circle is complete:

function parse(expr) {
	// Parse a configuration expression, using the current
	// application expression context to resolve any function
	// calls.
}

// These are "native" javascript commands that are exposed to the
// expression on evaluation. They act as the glue between
// expression-land and javascript land.

function subscribeTo(eventName, callback) {
	applicationEvents.subscribe(eventName, callback);
}

function showConfirmationDialog(message) {
	return confirm(message);
}

function setConfigValue(key, value) {
	config[key] = value;
}

expressionContext.register("subscribe-to", subscribeTo);
expressionContext.register("show-confirmation-dialog", showConfirmationDialog);
expressionContext.register("set-config-value", setConfigValue);

printButton.onClick(() => {
	const eventResponse = applicationEvents.broadcast("print-button-clicked");

	if(eventResponse) {
	    const text = textBox.value;
        print({ content: text, fontSize: config.fontSize, fontFamily: config.fontFamily});
	}
});
(set-config-value "fontSize" 14)
(set-config-value "fontFamily" "Arial")

; George's confirmation dialog
(subscribe-to "print-button-clicked" (lambda ()
	(if (show-confirmation-dialog "Are you sure you want to print?")
		true
		false)))

Although I’m greatly simplifying the process, the idea is there: code low-level primitive functions that allow a scripting language to control the application. I call this “full circle” because you end where you started: writing code.

It seems like a lot of work but, unlike the code running your application, you have complete authoritarian control over how, and what, your scripting language can do. This provides very tight encapsulation and, more importantly, prevents your auxiliary functions from trying to do too much with lower-level functions.

This concept isn’t new—EMACS (elisp), Excel (VBA), and the web (javascript) are prime examples of it—but it doesn’t seem as though many modern, big, single-page webapps have started to explore web frontend scripting languages yet. I plan to explore how useful the idea really is with PlateyPlatey over the next few weeks.