Idempotency and Immutable Objects

Thomas Hansen - Jun 2 '23 - - Dev Community

The last 3 weeks I have had on average 100+ additional followers on DEV.to on a daily basis. Realising DEV has a lot of young software developers, often times admiring us seniors, looking for advice on how to become a great software developer - I feel somewhat responsible for my followers. In addition I have been attacking most existing paradigms in the industry, without really providing alternative solutions.

I will therefor take some time to explain two of the most important constructs in software development, and why they are important, and how they work.

Immutable objects

An immutable object is an object that cannot have changes in its internal state after created. One of my favourite examples is System.String from .Net Framework. Every single property and field on this class is immutable. This implies that if you want to change a string instance, you cannot. Every single method on this class returns a new object and does not apply changes to the original object at all.

This has the side effect of that System.String is implicitly thread safe. Most problems originating from thread safety originates from multiple threads being able to change the same object simultaneously. If nothing can change the object, obviously two threads cannot interfer with each other here. Another bonus is that the class becomes simpler. For instance, System.String implements the IClonable interface. However, the implemantation of this interface is literally as follows.

public string Clone()
{
    return this;
}
Enter fullscreen mode Exit fullscreen mode

Obviously the Clone method does not do what it promises, simply because it doesn't have to. Every single other method in the class returns a new instance of the type, and cloning therefor becomes irrelevant. Choose to create immutable types whenever you can. It solves hundreds of problems you wouldn't normally even realise you have before 6 months down the road. Immutable types should be your "default choice" because it results in more robust software systems further down the road.

Idempotent methods

Idempotent is one of those weird words few knows what implies. My primary example of idempotency is the HTTP standard's PUT method's description. For the record, most software developers aren't able to correctly apply idempotency, so don't assume every PUT method is idempotent, unless explicitly documented as such.

Idempotency is why most "CRUD update endpoints" uses the PUT verb, or at least should use the PUT verb. An update SQL for updating fields on a single database record is idempotent by default, and you have to add weird stuff to it such as triggers to remove idempotency from it. Imagine the following SQL statement.

update table1 set column1 = 5 where id = 3
Enter fullscreen mode Exit fullscreen mode

Regardless of how many times I run the above SQL statement, the column1 column for the record in my table1 table having an id of 3 will always be 3. This solves a whole range of problems, such as for instance transactional problems, where we have a piece of code that needs to invoke multiple other micro services for the system as a whole to be in a good state. Imagine having a banking system where you need to transfer $5 from account "a" to account "b". This requires two changes to two different records.

If your code is not idempotent, and some error occours after having deducted $5 from account "a", you cannot simply run the method once more, because that will deduct an additional $5 from account "a" before it inserts $5 into account "b". If the method was idempotent though, you could just "rerun" the same method, at which point the $5 deduction from account "a" would not happen again, but only the last parts of the method actually applies state changes to your database by adding $5 to account "b".

Idempotent methods makes it much easier for you to create "long transactions", because you can replay your methods without ending up in an invalid state.

Conclusion

If you want to create robust software, both immutable types and idempotent methods and functions becomes crucial. Maybe not immediately, because as we all know "it works on my system" - But when you deploy your software into a Kubernetes cluster, with load balancing guaranteeing you that you've got state changes not propagating to all containers, and you've got 500,000 threads hammering your web APIs simultaneously, the fact that your code works on "your machine" becomes irrelevant. Idempotency and immutable types solves such problems before they even arrive.

When we built our ChatGPT chatbot both of the two above constructs were applied to its maximum extent. This resulted in a multi tenant, multi user solution, that "simply works". No need to debug hard to track down bugs, race conditions, or dead locks, because the two above constructs results in that such problems cannot even happen in theory.

Choose idempotency and immutable types where you can!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player