Composition is King!

“sheet, music” by Sesc em São Paulo is licensed under CC BY-NC-ND 2.0

Composition : “The way in which something is put together or arranged”

Composition is at the heart of many software engineering tasks. It is a technique that we apply in our daily work, though it is often implicit in our design choices, rather than being something we vocalise explicitly. Despite being king, I think we take its significance and prevalence for granted.

What does composition mean to me?

The answer to that depends on which metaphorical hat I’m wearing.

When writing code, I try to keep two compositional techniques in mind. Function composition and favour composition over inheritance. The former is powerful technique which originates from mathematics and which involves combining functions to create other functions. It’s best explained with an example.

Let’s imagine that you’re a salesman and you get a 3% commission, per month, on sales over £5000. In mathematical terms, you could write this as.

g = x – £5000, f = 0.03 * x  and therefore commission = f(g(x))

Given sales of £10,000, the calculation would be 0.03*(10000 – 5000). The result being a commission of £150.

let commissionRate = 0.03
let minimumSaleThreshold = 5000.00
let calculateCommisionableAmount (totalSales : float) =
let amount = totalSales - minimumSaleThreshold
match amount with
| amount when amount > 0.000 -> amount
| amount when amount <= 0.000 -> 0.00
let applyCommissionRate (amount : float) =
amount * commissionRate
let calculateCommission (totalSales) = applyCommissionRate(calculateCommisionableAmount(totalSales))
10000.00
|> calculateCommission
|> printfn "Commission is %f"

Though the above example is mathematically focussed and written using a functional programming language, the technique can be applied to many other problems and languages. A text parser, for example, could be written using function composition.

The latter technique, which will be familiar to anyone writing code using an OO language, is favour composition over inheritance. The idea, which has become a mantra to many, is that by using composition in place of inheritance we gain flexibility, whilst still achieving code reuse. If applied correctly, our code becomes more cohesive and more easily testable.

Like many software engineers, I sometimes need to shift my focus and dabble with a scripting language. Whilst donning that hat I try to give time to choosing the appropriate level of modularity, so that scripts can be developed and executed individually as well as being composed with the other scripts to form pipelines or tool chains. Decomposing a problem by creating scripts that do one thing well enables me to test and apply a scripts in isolation, whilst also giving me the capability to achieve more complex tasks through composition.

On a different day I find myself in a continuous delivery engineer’s role, applying composition to our CI/CD pipeline. Wearing this hat, I search for fine grained control mechanisms, frequent feedback loops and templates to enable reuse. The outcome I strive for is many small, repeatable, re-usable and potentially individually executable steps, but which can be composed to form a more complete CI/CD pipeline. CI tools, such as TeamCity, support composition through a range of features such as build configuration templates, shared VCS roots, build chaining and build steps. In Octopus deploy, Steps are one of the units of composition that provide the fine grained control and re-use mechanisms needed for a well structured CD pipeline.

Switching hats one final time, I find that I give considerable thought to the right composition when making architectural decisions. Building a system made up of smaller discrete services that can scale and flex appropriately, that can be changed and deployed independently, that are resilient and fault tolerant but which can still be reasoned about, is an act of composition (or decomposition if moving away from a monolith). Trade offs are frequent, as are musings of diagrams on whiteboards.

Tip of the iceberg

There is no shortage of other examples of composition in our industry. The increasing adoption of infrastructure as code sees tools like Terraform and the Serverless framework leverage composition techniques to provide modularity and reuse. Dependency Injection containers construct a requests object graph based on configuration at the composition root. Modern component based UI frameworks have seen stratospheric growth by making it simpler for developers to compose applications from discrete, reusable parts. And it wouldn’t be appropriate to leave out the fact that the Unix Philosophy has composition at it’s heart.

The many hats that modern software engineers wear today results in them applying composition techniques more often than they acknowledge. It’s a critical part of our jobs, of problem solving, of crafting great software and it’s reach goes further and wider than most of us credit.

Leave a comment