Content

Abstractions and Systems -- Part 2

Context

In Part 1 of this blog post, we discussed at a very high level, why abstraction is useful. In the process, we also discussed Systems thinking.

In this post, we’ll go through real life examples of abstractions and systems.

But before that, let’s define these terms.

Abstraction is hard to define, and there in it lies the reason of why it’s such an overloaded word in a software engineer’s day to day. What does it really mean though? To me, abstraction is just adding a layer of “modeling” on top of your software in order to simplify the complexity that would happen without it. Layer and modeling here are context dependent, and we’ll go through some examples of these.

It’s important to note that abstraction itself adds cost: it needs to be carefully designed, implemented, and people need to understand it. The tradeoff is if the cost of adding an abstraction is higher than the cost of not having that abstraction. If not, and if you decide that abstract layer, it may prove very useful once abstraction is just another component in your software, and that’s where the value of abstraction is.

Systems thinking is a kind of abstraction, and the one we are really interested in. Here, the modeling layer is the system. If the system is easy to use, users of the system don’t care about its internals, only the input/output contract. Systems can have state as well, and users may care about that depending on how they’re using the system.

Modeling as a system

Let’s illustrate a system:

Mathematically, for a stateless system:

\[f(x) = y\]

Or for a stateful system:

\[f(x, state) = y\]

Here, the function f is just a mapping from what’s called a domain to a range.

Visually:

This is basically saying that function can take N (>= 1) inputs, and map to 1 output. Not the other way around! The mapping must be N:1, not 1:N. The latter means the function is not deterministic i.e. we don’t know what’s the output for a given input. By definition, 1:N relationships are not functions.

General pattern

A general pattern of adding an abstraction (system) is as follows. Suppose you have two components in your application:

We replace it with:

When we do this, clearly it’s a new system we need to understand. So why do this?

Pros

  1. Each component needs to understand a common abstraction system, not other components. In other words, components are decoupled.
  • As a result of above, components can evolve independently, without breaking the overall system or depending on each other.
  • Communication between components is standardized, since Abstraction system can act as a broker.

Cons

  1. When an abstraction changes, components that interact with it may have to change as well. That’s why it’s generally hard to make backwards incompatible changes in abstractions.
  • There’s a cost to adding a new system. Since this system will serve multiple components, it has to be carefully designed, implemented, and maintained. Finally, components have to understand this systems and any concepts that come with it, which adds mental cost.

By the way, people sometimes use the word interfaces to mean such abstract systems as well.

Examples

This post became longer than I had initially anticipated. So I’m going to move the examples to a separate part.