One abstraction to rule them all

Posted on Mar 28, 2026

I have been trying to fit all of software engineering in my head at once. Not memorize it, but just have one idea that makes everything click a bit more.

I am not saying this is the answer to everything, but it has been useful enough that it made me want to write it down.

“Everything is a boundary with invariants, exposed through an interface.”

Thats it. I am going to try to explain these 3 concepts: boundary, invariants and interface a bit better.


Boundary

A boundary is just a line that says: this is mine, that is yours.

Inside the boundary, a system owns its stuff. Lets say for example its memory, its files, its state, etc. Nothing outside can touch it directly. That is the whole point of boundaries.

A Linux process has its own memory space, meaning code cannot accidentally (or intentionally) read another process memory.

A database transaction has a boundary, changes inside it are invisible to everyone else until you commit.

A container has its own filesystem, its own network, its own process table.

When you violate a boundary, things break in ways that are really hard to debug, because now two systems are sharing state.

Invariant

An invariant is a rule that must always be true in order to keep the system integry otherwise the system is broken.

A database has invariants: a foreign key must point to something that exists, a NOT NULL column must have a value, a balance cannot go below zero.

A cache has invariants: it can only hold N items, items expire after T seconds.

A real-time system has a deadline as an invariant: if this task does not finish by 10ms, then it is consired wrong.

The interesting thing is that deadlines are invariants too. Most people think of deadlines as a project management thing but they show up everywhere: TCP retransmit timeouts, HTTP request timeouts, Kubernetes liveness probes, scheduler time slices. A hard deadline means: if you miss it, the output is worthless. A soft deadline means: if you miss it, the output degrades. Both are invariants, just with different consequences for violation.

Interface

An interface is the contract between a system and the outside world.

It says: here’s what you can ask me to do, here’s what I need from you, here’s what I’ll give back, and here’s what I’ll tell you when something goes wrong.

Everything else, how it is done, what internal data structures are used, what language it is written in, is hidden hidden behind the boundary and none of your business.

A REST API is an interface. A syscall is an interface. A function signature is an interface. An SQL schema is an interface. A USB port is an interface.

The reason interfaces matter is that they let you swap the implementation without breaking anything that depends on it. If you change internals but keep the interface identical, callers don’t care. If you change the interface, every caller breaks. This is why breaking changes in APIs are such a big deal, the contract was broken.


Everything maps to these concepts

While this is associated more with software patterns or architecture, you can see it in everything.

Take the OSI model, that describes how network communication works, from physical cables up to the apps you use. It has 7 layers. Each layer is a bounded system. Each layer only talks to the layers directly above and below it (that is the invariant).

Each layer communicates through a defined format, Ethernet frames, IP Packets, TCP segments, HTTP messages (those are the interfaces). You can swap out WiFi for Ethernet at layer 1 and HTTP does not care, because the boundary held.

Take a Linux process. Its boundary is the virtual address space, meaning it thinks it owns all the memory even though it’s sharing the physical machine with other processes, the OS handles the illusion. Its invariants are the resource limits: how much CPU time, how much memory, how many open file handles. Its interface is the syscall ABI, meaning the set of calls it can make to the kernel: open a file, write to stdout, spawn a process, allocate memory.

Take a pure function, a function with no side effects, meaning it doesn’t touch anything outside itself, does not modify global state, does not do I/O and given the same input always returns the same output. Its boundary is the stack frame, meaning it has its own local variables and nothing else. Its invariant is exactly that: no side effects. Its interface is just the type signature, what goes in, what comes out.

Take authentication and authorization. Authentication is the question: who is crossing this boundary? Authorization is the question: does this identity’s invariants (its permissions) allow this interface call? IAM roles, RBAC, JWT tokens, all of these are just ways to encode “who you are” and “what you’re allowed to do” in a form that can be checked every time something tries to cross a boundary.

Ok… What can I do with this?

Once you see the pattern, things become mechanical.

  1. Decompose anything

When you encounter a system you do not understand, ask three questions: what does it own exclusively (boundary)? what rules must it never break (invariants)? what is its published contract (interface)? You do not need to read the source code. You do not need to understand the internals. Answer those three questions and you have a working mental model.

  1. Compose systems correctly

Every architecture decision is really just: can I connect system A to system B without either of them breaking their invariants? If system A needs to call system B, it has to go through B’s interface. If it reaches inside B’s boundary directly, you have created a hidden dependency that will bite you later. Microservices, module boundaries, database schemas, API versioning, all of this is just the same question asked in different contexts.

  1. Schedule like a computer

Every decision about what to do next, whether you are a CPU, a tech lead, or a person with a todo list, is a scheduling problem. You have bounded resources (time, CPU, money) and tasks with invariants (deadlines, dependencies). The right order is the one that minimizes the harm from missing constraints. In practice: do the thing whose failure would cause the most damage first. This sounds obvious but most people do not actually do it. They do the easiest thing, or the most recent request, or whatever is loudest.

Where this all goes down

I want to be transparent and say that this is not a universal hammer, there are cases where this model does not work.

It does not explain emergent failures. Sometimes every single system is behaving correctly, honoring its boundary, not violating its invariants, responding properly to interface calls, and the whole thing still falls over. Cascading failures, thundering herd problems (where many systems simultaneously hammer one system that just came back online), distributed deadlocks. These happen between systems, not inside them. The model describes structure. It does not describe dynamics.

It does not tell you where to draw the boundaries. This is the actually hard part of software design, and this model gives you nothing. Should user profiles and authentication be one service or two? Should this be a method or a module? Should this database schema have one table or three? Two engineers using this exact mental model will give you completely different answers. The model tells you how to reason about a system once it exists. It does not tell you how to design one from scratch.