Indirection

5 March 2024

“All problems in computer science can be solved by another level of indirection.” – David Wheeler(?)

All problems? Hm… let’s see; let’s try a few.

Suppose we’re building an authorization module to control User access to resources or actions on those resources. For this, Users need to be assigned permissions; depending on the permissions a User has, it can be granted (or not) access to a resource.

Let’s give it a first stab:

type Permission = ReadFile | WriteFile
type User = {Id: UserId; Permissions: Permission list}
let u1 = {Id = "123"; Permissions = [ReadFile; WriteFile]}
let u2 = {Id = "234"; Permisisons = [ReadFile; WriteFile]}

Ok, but, unsurprisingly, requirements change. We are now told we need more fine grained access control. We need to replace ReadFile with DownloadFile and ViewFile. Hm… oh no, we need to change all the Users!

Instead of having a direct relationship between User and Permission, we could have an indirect relationship via a new Role reference type:

type Permission = ViewFile | DownloadFile | WriteFile
type Role = {Id: RoleId; Permissions: Permission list}
type User = {Id: UserId; Role: RoleId}
let reader = {Id = "role1"; Permissions = [ViewFile; DownloadFile]}
let u1 = {Id = "123"; Role = reader.Id}
let u2 = {Id = "234"; Role = reader.Id}

Now the Role can change without impacting User. Here’s a graphical depiction of what we’ve done:

Now let’s implement a text editor with the ability to set different font formats on pieces of text. Simple:

type Format = Regular | Bold | Italic
type Text = {Text: string; Format: Format}
let text1 = {Text="Hello"; Format=Italic}
let text2 = {Text="World."; Format=Regular}
let text3 = {Text="We have been bad to you."; Format=Italic}

After writing our text, we decide that maybe Bold is better than Italic for this passage. Damn, now we need to go and find all those pieces of texts we had set to Italic and change them to Bold.

Instead of having a direct relationship between Text and Format, we could have introduced an indirect relationship via some Style intermediary:

type Format = Regular | Bold | Italic
type Style = {Id: StyleId; Format: Format}
type Text = {Text: string; Style: StyleId}
let strong = {Id= "strong"; Format = Bold}
let normal = {Id= "normal"; Format = Regular}
let text1 = {Text="Hello"; Style=strong.Id}
let text2 = {Text="World."; Style=normal.Id}
let text3 = {Text="We have been bad to you."; Style=strong.Id}

Great! Now we can change the Style once and in one place, and affect all Texts with that Style, instead of having to find and change every piece of text with a specific Format.

Let’s look at one more. Suppose now that we’ve built a web service called “FoamBnB”; it connects house owners looking to rent their property with traverlers looking for a temporary place to stay. Our first deployment architecture looked like this.

Everything was going great for the first few months, but the site became so populare that our server could not handle the load of requests from the clients. What to do? Can indirection help us here? Let’s add a load balancer and a bunch of instances of our service behind it. The load balancer can now evenly distribute the load across the service instances.

The all in “All problems in computer science can be solved by another level of indirection.” may be too big a claim, but as we’ve seen indirection certainly can help in a variety of problems in different domains.

All the diagrams above are special cases of the following general relationship:

R hides Y from X, allowing us to change Y freely without X caring or even knowing about those change.

Spread the Word