This article should not be mistaken to any design pattern but its arguably a good practice that you can adopt while writing functions, designing components, or even architecting a system. The major theme is around configurations which are generally found being used across programming languages.
Whats a Configuration
It can be as simple as a JSON object with a bunch of key values or can be a configuration object managed by a full blown configuration management system. Definitely there is no right or wrong way to define or store a configuration. It’s just a representation of some meta data.
Who’s a Consumer — Anyone
A configuration consumer can be anything. It can be simple function, a component or an application. Even there can be multiple consumers for a single configuration. It’s just that the power lies in the consumer & not the configuration. The configuration doesn't dictate anything to the consumer, the consumer can decide what to infer. The configuration alone is just a meta data, the consumer can choose what to infer out of it.
What a Configuration means to the Consumer?
Two things, one it can be treated like an execution context. The configuration can hold data which can be used by the consumer to produce results based on it. Second, it can be treated as behaviour. The configuration can be used to determine the behaviour of the consumer. Both of these sound very similar right? Parameters & Interfaces?
Parameters passed to a function allows to set something in the execution context. It allows the caller to pass on some data to the consumer.
Interfaces determine the behaviour of the implementor. The implementor is in contract with the interface to implement a set of behaviour (functions).
A configuration can definitely passed to a function to trickle down data to its execution data. But also it can serve an entire different purpose to another consumer.
There is a very big tradeoff for flexibility if you notice. The author must take the responsibility of honouring the configuration, the configuration cannot alone dictate the rules to the consumer. Thus, the configuration is just the describes the What & the consumer describe the How. By now you would have realised that configuration allows you to practice separation of concern in your code.
Understand By Example
Let’s assume you have to create a house object which contains some properties like floors & garden. Where floors could contain an array of Room objects, etc. So the first method would be to do it in an imperative way, where you based on the requirement, step by step construct the object by creating all required sub properties & linking these objects in the desired manner. The below example is fairly simple, done in an imperative fashion.
Now whats the problem?
Some obvious deductions would be
- Lacks upfront information. The reader has to go through each line to understand what type of house is being constructed. Hence, you need to create some sort of mind map to make sense of the code. For example, you discover that the house contains a garden at the very end. So there can be suspenses while you read code from top to bottom. Suspenses are awful for the reader, makes the reader more cautious & hence decisions takes more time.
- Forced information on the reader. Not everyone is interested in the How, probably the reader want to know What & going one step ahead maybe Why. This code forces the reader to go line by line & leaves the reader to interpret the result. There is some overlap between the What & How, its a liability for the reader to distinguish.
- It’s a shameless abuse of the DRY principle. The code is not tangible, its not at all reusable, if you want to create a new house, you will have to again write line by line. This lacks an entry point which can produce the same result.
Obviously there is a better way to do it. Here is a better version of it using a simple configuration -
Definitely the by just looking at the code, its looks more expressive. There are better parts of using a configuration in this simple example itself which are discussed below-
Configuration decouples implementation detail of the consumer from the intended behaviour. Hence the behaviour can be shared by different consumers allowing you to retain the same configuration & have multiple implementations build on top of it. Assume you have written a configuration for creating an object. There can now be multiple consumers from different vendors. Example for house object , there can be multiple builder functions.
If you see, this is a powerful system. This gives you flexibility to create multiple implementations over the same configuration. This should not be mistaken to interfaces. Interface is an abstraction of behaviour alone, that way you can say Interfaces are configuration but the opposite does not hold true.
Interfaces have a more definite purpose & quite tightly tied up with the language itself. Configurations on the other end are quite open, it can be isomorphic shared by two entirely different systems. Serialisable nature of a configuration allows you to go one step ahead & use it across systems which cannot talk to each other directly.
Example, the same configuration can be consumed by a UI component, application state management level & at service level. Consider filters in a flight booking website. These filters can be governed by a configuration. So you don’t need to write any code. Just add a configuration which can be consumed by UI component, to display the new filter & determine payload for events to dispatch on click. The application state management middleware can rely on the same to determine what action to be taken on the events. At service level, you can use to construct query for the database access.
A configuration is not limited to behaviour. It’s at the authors discretion to use the configuration to define behaviour or as simple input to the consumer of the configuration.
Predictable Behaviour, Separation of Concerns
The reader gets upfront information of what the author is trying to do. The focus shifts to the What of the code from the Why. The decoupled behaviour & context of the consumer brings more clarity in the code. The reader knows where to find the What & How of the code.
The practice of writing configurations makes the developer evolve the mental model of coding to adapt a more declarative approach.
Less Surface Area, Less Maintenance Weight
The configuration becomes the source of truth (The credibility of the configuration is still dependent on the consumer). To make any addition or change, you can always rely on the configuration to make the update.
The configuration exposes the surface area for modifying your code. You can alter the execution context or the behaviour of the consumer. Again the extent to which you can bring in variation to this code depends on the consumer. Just adding configuration will not make the consumer any good. The consumer must support the flexibility. Designing the a configurable consumer itself deservers a separate blog or even a book. Again it can be a simple consumer like a utility function or also a configurable workflow system.
At Kubric we have evolved complex configurations to language to express application state, functionality & workflows in our UI. Will be sharing more about such patterns & useful home brewed techniques we have developed over a period of time.
You can reach out to me on twitter! Have a great day!