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 Text
s 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.