A Matter Of State

A

Many online tutorials that teach how to build software focusĀ on the storage, exchange and visualization of state. They teach how to use databases, the CRUD pattern, Represential State Transfer, the React/Redux UI stack. All these tutorials have one thing in common.

They all focus on state... but state has an inherent problem.

Before we dive into the problem, lets first set a baseline on what state is.

What is state

State represents a set of information known for a given entity at a given point in time and location.

For example, a system could have a Person entity containing current personal information. Formatted like JSON it could look like this.

{
    id: 12345,
    name: "Goeleven",
    firstName: "Yves",
    address: {
        street: "Somewhere",
        number: "45",
        postalCode: 1234,
        city: "SomeTwn",
        country : "Belgium"
    }
}

Familiar right?

The problem with state

The inherent problem with state is:

Whenever components get multiple versions of state, they don't know what happened while transitioning and run into conflicts.

Especially in distributed systems, or apps that go offline, this problem becomes more apparent. As different components of the system will work with different versions of state at different times and/or in different locations, this will lead to conflicts.

The problem with state

These conflicts are hard, or even impossible to resolve.

Example

When a component, which initially received the state above, would get the following two states concurrently, from different users or systems. Would it know which one is correct? And what to do with them?

{
    id: 12345,
    name: "Goeleven",
    firstName: "Yves",
    address: {
        street: "Somewhere",
        number: "45",
        postalCode: 1234,
        city: "SomeTown",
        country : "Belgium"
    }
}
            
{
    id: 12345,
    name: "Goeleven",
    firstName: "Yves",
    address: {
        street: "SomewhereElse",
        number: "18",
        postalCode: 3456,
        city: "SomeOtherTown",
        country : "Belgium"
    }
}
            

Probably not, for machines these values are hard to interpret.

For us humans it is easy to identify the difference.

The one on the left is the correction of a typo, and the one on the right indicates that the person relocated.

The state on the right is clearly more important than the one on the left, in fact it may even be an opportunity in disguise that the system could have responded to if it could have differentiated these states.

Capture transitions explicitly

The solution to this problem is to capture the transition steps between the states explicitly.

Make transitions explicit

Based on the current state:

  • A user invokes an action to issue a command to the system. By creating a command you can encapsulate the users intent. This command could be executed locally or transported elsewhere for execution.
{  id: 12345, commandType: "CorrectAddress", newCity: "SomeTown" }
  • The system makes a decission whether it can, and wants to, execute on the command and records its decission, as an event. This event can be kept locally and/or communicated to any component that needs to know.
{ 
    id: 12345, 
    eventType: "AddressCorrected",
    when: "2021-02-26T15:06:23Z",
    oldAddress: { street: "Somewhere", number: "45", postalCode: 1234, city: "SomeTwn", country : "Belgium" },
    newAddress: { street: "Somewhere", number: "45", postalCode: 1234, city: "SomeTown", country : "Belgium" }
}
  • Other components in the system can now compute the new state, by either folding the known state and the event, or by replaying all past events. This act is often called a projection.
{
    id: 12345,
    name: "Goeleven",
    firstName: "Yves",
    address: {
        street: "Somewhere",
        number: "45",
        postalCode: 1234,
        city: "SomeTown",
        country : "Belgium"
    }
}

Conflict resolution

With the transitions explicitly in place, the system can now make automated decissions when there are any conflicts, by interpretting and ordering the events accordingly.

Conlict resolution

Possible strategies to resolve conflicts based on events:

  • Different events that apply to different parts of the state, e.g. PersonRenamed vs PersonRelocated. These can be serialized in any order, there really was no logical conflict.
  • Different events that apply to the same parts of the state, e.g. AddressCorrected vs PersonRelocated. These require re-ordering, most important one last: PersonRelocated should be applied after, AddressCorrected.
  • Same events that apply to the same parts of the state, e.g. PersonRelocated vs PersonRelocated. Dependent on the scenario these can be serialized in order of occurance.
  • A final option is to leave the conflict as is, make a new decission which rectifies the state and add the result as a third event.

Event Driven Architecture

You can build a distributed system, by passing state, commands or events around between the different parts. And all three are valid for given scenarios.

But when I can I will default to passing events around as they give me the most flexibility for resolving any future problem. Worst case it is also possible to rebuild the state of the entire system when you still have the original events around.

What do you default to? And why?

About the author

YVES GOELEVEN

I've been a software architect for over 20 years.

My main areas of expertise are large scale distributed systems, progressive web applications, event driven architecture, domain driven design, event sourcing, messaging, and the Microsoft Azure platform.

As I've transitioned into the second half of my career, I made it my personal goal to train the next generation of software architects.

Get in touch

Want to get better at software architecture?

Sign up to my newsletter and get regular advice to improve your architecture skills.

You can unsubscribe at any time by clicking the link in the footer of your emails. I use Mailchimp as my marketing platform. By clicking subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp's privacy practices here.