A115 Logo Dark A115 Logo Light

A115 is a London-based consultancy helping fast-growing businesses and scale-ups build sophisticated on-prem or cloud-based technology platforms when code quality matters.

We specialise in rapidly building, securing and scaling cloud-native greenfield platforms to add new capabilities or expand into new markets. With over 20 years' experience productionising experimental code and complex systems, A115 provides end-to-end services - from initial architecture and infrastructure design through development, deployment and maintenance. Leveraging expertise across industries and technologies like Python, SQL, AWS and Azure, A115 enables clients to launch innovative products faster while ensuring robustness, resilience and regulatory compliance.


Training and Consulting

Bookmark this page and check again soon for our personalised individual or team training packages and offers!

London, United Kingdom (HQ)

Code quality

Reliable Business Software with Immutability

How referential transparency and thread-safety help reduce risks and costs

Modern business software must handle rapidly changing requirements, large datasets, and multi-user concurrency. Bugs that corrupt shared data can be devastating in production systems. This introduces reliability risks and auditing challenges in industries like healthcare, finance, e-commerce, and more.

Using immutable data structures mitigates these issues through referential transparency and thread-safety. By locking down data after creation, unwanted side-effects that introduce bugs are avoided. The resulting code is easier to reason about, test, audit, and scale.

For example, healthcare software that tracks patient lab records cannot risk accidentally overwriting data. Financial systems require auditable logs of all transactions. Immutability principles enable these capabilities while streamlining development.

This guide explains immutability concepts for building reliable business software. You'll learn how immutability facilitates domain-driven design, reduces testing costs, and enables scalability across concurrent systems - all critical for enterprise development. Follow along in Python examples as we contrast mutable and immutable data structures in action.

Immutability in Action - A Database Analogy

To understand immutability, consider the analogy of a database table that tracks financial transactions. Each row represents a transaction with details like date, amount, source account, destination account, etc.

If this mutable data can change unexpectedly, it introduces risk:

  • Buggy code could accidentally overwrite records, losing important audit trails or even money!
  • Concurrent writes from multiple servers could corrupt records and balances.

A database admin would lock this table as read-only to avoid these issues. New transactions can only be inserted, never updated. This immutability ensures data integrity.

We want similar guarantees in our code. If objects that represent domain concepts like Transaction just expose getters, side effects are avoided. We can add logic that safely derives updated state instead of mutating in place.

This is the power of immutability - locking down objects to control how state changes while avoiding corruption. Next we'll see Python code examples contrasting mutable and immutable techniques for modeling business data.

The Not-So-Great Way: Modeling Transactions with Mutable Dicts

Let's model the transaction database table from before in Python code. We'll start by representing each transaction as a dictionary with keys like "id", "amount", "source", etc. In Python, normal dicts are mutable - we can change their values.

We'll put these dicts into a list to track multiple transactions:

t1 = {"id": 1, "amount": 5000, "source": "A"} 
t2 = {"id": 2, "amount": 2000, "source": "B"}

transactions = [t1, t2]

This is simple and flexible. But there is risk - any part of the code can directly change data. Let's say we calculate the average transaction amount with this Python code:

average_transaction = sum(
    t['amount'] for t in transactions
) / len(transactions)

So far so good. But imagine some time later, perhaps elsewhere in the code, someone modifies the amount of one of the transactions, so we end up with this:

t2["amount"] = 9999 # accidentally corrupt transaction!

Now transaction 2's amount is doubled with no audit trail. In addition, our previously calculated average is no longer accurate. This is the danger of exposed mutable data - it can lead to bugs that introduce incorrect state changes.

What if multiple threads or servers concurrently try to update balances based on transactions? Data corruption can happen due to race conditions.

To avoid these issues, we need to control mutation exposure. Next we'll explore techniques using immutability.

A Better Solution with Immutable Namedtuples

We can improve our transaction model using Python's namedtuple, an immutable type from the standard library collections module:

from collections import namedtuple

Transaction = namedtuple('Transaction', ['id', 'amount', 'source'])

t1 = Transaction(id=1, amount=5000, source="A")
t2 = Transaction(id=2, amount=2000, source="B")

transactions = [t1, t2]

Now each transaction is immutable. Trying to modify one raises an error:

t2.amount = 9999 # fails with AttributeError

This avoids accidental corruption of transaction data. To update state, we derive new tuples instead of mutating in-place:

t2_updated = Transaction(id=2, amount=9999, source="B")

The original is untouched. This code is safer with no exposed mutation. Logic that calculates updated state stays isolated.

Furthermore, if we want to have a function which processes a transaction, we can now annotate the argument as type `Transaction` instead of just `dict`, which is both more readable and safer. Because Transaction is a `namedtuple`, any such function would be forced to process the Transaction data and possibly return a new copy of a Transaction object - but it has no way of modifying the original transaction object. This can not be said of dicts.

However, our transactions list is still mutable. We could still accidentally append/remove elements. Next we'll look at controlling entire collections.

Full Lockdown: Immutable Transaction Collections with Tuples

While our individual Transaction namedtuples are now immutable, the list collecting them is still mutable:

transactions = [t1, t2]

We can still do dangerous appends or deletes on this list. To make our entire transaction set immutable, we convert to a tuple:

transactions = (t1, t2)

It's a small but powerful change. Now our data structure is locked down completely. Tuples don't allow item addition/removal after creation. We can derive new tuples with added transactions:

new_transaction = Transaction(id=3, amount=7500, source="C")
updated_transactions = transactions + (new_transaction,)

The original tuple remains untouched. Code that calculates new state works with immutable data it cannot corrupt.

By using immutable namedtuples and tuples, we model business data safely. Our average transaction calculation, for example, is now completely safe from accidental modifications. Auditability, testing, and multi-threaded access become easier and more reliable.

Immutability Enables Reliable Enterprise Software

We've seen immutable techniques for safely modeling business transactions in Python. These examples demonstrate core principles for building reliable enterprise systems:

  • Isolate mutable state and control access to avoid corruption
  • Derive new state instead of mutating in place
  • Make domains and aggregates immutable to minimize side effects
  • Maintain audit history by deriving new immutable values
  • Avoid race conditions with thread-safe immutable objects

Following these patterns facilitates domain-driven design. It makes code more readable, reusable and testable when logic relies only on getter access to immutable state.

These techniques enable developing microservices and distributed systems by avoiding shared mutable state across services. New state can be safely derived from events and commands instead.

Immutability may involve some initial overhead to learn. But the long term benefits are substantial for performance, scalability and maintenance of business systems.

The next time you model a domain entity in code, consider making it immutable. Think about how to safely derive state changes externally instead of allowing internal mutation. Mastering this paradigm unlocks reliable enterprise software.

How Immutability Ties Into Functional Programming

The techniques we've covered connect deeply to functional programming concepts. By isolating state and avoiding side effects, we gain "referential transparency" - the ability to reason about code as simple input-output transformations.

For example, with immutable transactions, a function that calculates tax could rely purely on transaction amounts without worrying about external state changes. This makes testing and optimization easier.

Python supports a functional style with immutable data structures, pure functions, recursion, map/filter/reduce, list comprehensions etc. Libraries like PyToolz provide more advanced functional data structures and functions.

In a purely functional paradigm, even collections are immutable. There are, for example, Python libraries offering persistent vector/map implementations with structural sharing for efficient derivation of updated states.

So in Python we get immense flexibility - incorporate functional approaches only where practical. Use immutable data structures for core domain models, isolate side effects, and keep business logic referentially transparent.

The result is code that's simpler, safer and scalable - critical for enterprise systems that must continue evolving for years. Immutability paves the road to robustness.

With more than 20 years experience consulting some of the fastest growing brands in the UK and globally, the founder of A115 has a no-nonsense educational approach to modern enterprise software engineering.

Python Training
Bespoke Software
Code quality
Case Studies
Industry Insights

Subscribe to the A115 weekly newsletter!

By subscribing you agree to our Privacy Policy