State Is the Root of All Evil
Find the English version below
Hi ,
Was unterscheidet eine Anwendung von einer (mathematischen) Funktion?
Eine Funktion nimmt einen Input x
und transformiert ihn zu einem Output. Wenn gilt f(x) = 2 * x
, dann ist das Ergebnis dieser Funktion bei der Eingabe von jedem x immer gleich.
Es gibt keine Seiteneffekte. Wir nennen dieses Verhalten stateless.
Aber was hat das mit der Frage vom Anfang zutun? Eine Anwendung geht darüber hinaus. (Fast) jede Anwendung muss mit State arbeiten. Sobald wir I/O Operations in unserem System haben, haben wir inhärent State. Und häufig kann ein methodenaufruf, mit dem gleichen Input, zu unterschiedlichen Zeiten, unterschiedliche Resultate liefern. Und das ist auch nicht vermeidbar und liegt in der Natur der Aufgabe, die wir lösen wollen.
Ein API call auf GET /users
wird immer ein anderes Resultat liefern, je nachdem welche User sich im System registriert haben. Das ist ja genau das, was es tun soll.
Aber was ist einfacher?
Natürlich ersteres.
Wenn unsere Methode stateless und damit deterministisch ist, dann ist sie:
- Einfach zu testen. Wir müssen nur alle Randbedinungen für den möglichen Input abdecken. Vielleicht ist die Menge an Inputs sogar endlich und wir können alle überhaupt möglichen Werte testen.
- Einfach anzupassen. Ein neuer Entwickler kann diese eine Methode in absoluter Isolution betrachten.
- Einfach zu parallelisieren. Wir können diese Methode mit so vielen Threads und Prozessen gleichzeitig ausführen wie wir wollen. Es kann zu keinem Konflikt kommen.
- Einfach zu cachen. Wenn der Output für einen Input immer gleich ist, dann können wir uns den Output merken. Er kann sich nicht verändern. (Memoization ist hier das Stichwort.)
Alles wird schwieriger, wenn wir state haben.
Vor ein paar Tagen habe ich mich mit Code beschäftigt, der unnötig viel State transportiert hat. Ich war auf der Suche nach einem Bug.
Dieser Code hat sich einen Zustand, der aus der Datenbank kommt, an mehreren Stellen erneut gemerkt. Es war nur ein Boolean. Aber über mehrere Klassen hinweg wurde diese Information dupliziert.
Das Resultat war sehr unübersichtlicher Code. Ich konnte schnell lokalisieren an welcher Stelle genau der Bug auftritt. Aber herauszufinden wieso er dort auftritt hat mich viel Zeit gekostet. Ich musste genau tracken wann der state manipuliert wird. Irgendwo musste es vergessen worden sein.
Dabei wäre das alles nicht nötig. Der State war nur in der Datenbank nötig. Es hätte ausgereicht ihn am äußeren Layer - der Datenbankschicht - zu halten. Und dann wird er als Parameter immer tiefer in die dahinter liegenden Layer gereicht.
State is the root of all evil
Sobald state im Spiel ist wird alles schwieriger.
Deswegen musst du state so lange vermeiden wie es möglich ist. Das erfordert am Anfang von dir eine andere Denke - aber es wird dir schnell leicht fallen.
Rule the Backend,
~ Marcus
Hi ,
What distinguishes an application from a (mathematical) function?
A function takes an input x
and transforms it into an output. If f(x) = 2 * x
, then the result of this function for the input of any x is always the same.
There are no side effects. We call this behavior stateless.
But what does this have to do with the question from the beginning? An application goes beyond this. (Almost) every application must work with state. As soon as we have I/O operations in our system, we inherently have state. And often a method call, with the same input, can yield different results at different times. And this is also unavoidable and is in the nature of the task we want to solve.
An API call to GET /users
will always deliver a different result, depending on which users are registered in the system. That's exactly what it's supposed to do.
But what's easier?
Naturally the former.
If our method is stateless and therefore deterministic, then it is:
- Easy to test. We only have to cover all edge conditions for the possible input. Perhaps the set of inputs is even finite, and we can test all possible values.
- Easy to adapt. A new developer can consider this single method in absolute isolation.
- Easy to parallelize. We can run this method with as many threads and processes simultaneously as we want. There can be no conflict.
- Easy to cache. If the output for an input is always the same, then we can remember the output. It cannot change. (Memoization is the keyword here.)
Everything becomes more difficult when we have state.
A few days ago, I was dealing with code that had unnecessarily transported a lot of state. I was looking for a bug.
This code had repeatedly remembered a state that comes from the database. It was just a Boolean. But this information was duplicated across several classes.
The result was very confusing code. I was able to quickly locate exactly where the bug occurred. But figuring out why it occurred there took a lot of time. I had to precisely track when the state was manipulated. It must have been forgotten somewhere.
All this would not have been necessary. The state was only needed in the database. It would have been enough to keep it at the outer layer - the database layer. And then it was passed as a parameter deeper into the underlying layers.
State is the root of all evil
As soon as state is in play, everything becomes more difficult.
That's why you must avoid state as long as possible. This requires a different way of thinking from you at first - but it will quickly become easy for you.
Rule the Backend,
~ Marcus