← Back to list

Type state pattern in Ballerina

The type state pattern is an API design pattern that aims to encode runtime states in the compile time type. I came across this idea in the…

Heshan Padmasiri · 2022-10-30 07:58 · 97 claps · 3.4 min read
#ballerina #ballerinalang #state-management
Open on Medium ↗
Wiki topics: BIZ · Business Strategy 🌐 · Web Development

Type state pattern in Ballerina

The type state pattern is an API design pattern that aims to encode runtime states in the compile time type. I came across this idea in the excellent post the type state pattern in Rust by Cliff L. Biffle, where he describes the concept behind this pattern as well as how to implement this using the Rust programming language. I highly recommend going through that article for anyone interested in further exploring this idea. In this post, I will try to describe what I felt to be the natural way to use the type state pattern with Ballerina.

To better understand how to use the type state pattern let’s start with an API that doesn’t use this pattern. Assume we have a Connection object that can be of three states ( initialised, active and inactive) where it has the following state transitions.

State transitions and operations

State transitions and operations

Furthermore, we have two operations foo and bar where foo can be called in initialised or inactive states and bar can only be called in the active state as shown in the diagram. We can represent this using the ConnectionObject class as follows

[embed]

Here we have got two state transition functions (which will be referred to as transition functions from here onwards) activate and deactivate as well as corresponding functions for the foo and bar operators. At the beginning of each of them, we are checking if the current state is valid, and if not API will panic (we could also return an error which can later be handled as part of the control flow but it is irrelevant for our discussion here). There are two problems in this design

  1. All the state checks are done at runtime. This means we must have very good unit tests that validate API is behaving correctly in all states (including giving errors if in the wrong state). It also means we have a runtime overhead to do state validation.
  2. We can’t lexically determine the state of the object. This means IDEs will suggest operations that are not valid (based on the state) and the developer can’t just look at the code and determine the state without analysing the control flow first.

While at this state neither of these problems look like big issues as the number and nature (for example if the state is determined by more than one value) of the state becomes more and more complicated these could become big issues. This is where the type state pattern shines. Lets start by breaking the ConnectionObject class into 3 class ( InitialisedConnectionObject, ActiveConnectionObject and InactiveConnectionObject) representing each state.

[embed]

With that, we have gotten rid of the runtime checks. This is achieved by two things.

  1. Each class now only implements the valid transitions and operations for the corresponding state.
  2. Constructor of each class asks for an object of the previous state (for example ActiveConnectionObject asks for InitialisedConnectionObject) to prevent people from creating states out of thin air.

Since the state of the connection is now lexically determined IDE will only provide suggestions for valid transitions and operations while the compiler will show errors if we try to force them (compiler will complain even if you try to use a runtime cast, since it can reason one can never be the other). Unfortunately, this is also the point where we start running into a few problems. Since Ballerina doesn’t have a concept of ownership we can create multiple ActiveConnectionObjects from the same InitialisedConnectionObject whereas in Rust we can detect this at compile time. I shall further discuss this issue at the end of this post. The second problem comes from operations that are valid in multiple states such as foo. Since Ballerina doesn’t support implementation inheritance (reference) we have to duplicate the implementation of those operations. Also, note we will have to duplicate state transitions too if we have states that can be transitioned from multiple states (ex. if we can go to the inactive state from either active or initialised states). To take care of this duplication issue I think the best way is to follow a more functional approach such as,

[embed]

While this approach simplifies most of the boilerplate code it still leaves us with the problem caused by the lack the ownership. For example, fallowing code is still valid.

[embed]

At the moment it is not obvious to me how we can detect i1 has already been activated when we try to activate it for the second time with a2 at compile time. In Rust, we handle this by the activate function taking ownership of i1 thus compiler complains that i1 is no longer available after the first call to activate. Ballerina doesn’t have this ability. One compromise we can do is to have a second transitioned state that is checked at runtime. For example

[embed]

But this brings us back to runtime type checks and mutations of state objects (something we managed to get rid of as a side effect when we switched to the type state pattern). However, this means we have only two states to worry about at the runtime which is less than what we will have to handle otherwise.


메타데이터
post_id
6ccf3a21bbb
slug
type-state-pattern-in-ballerina-6ccf3a21bbb
url
https://medium.com/@hpheshan/type-state-pattern-in-ballerina-6ccf3a21bbb
canonical_url
https://medium.com/@hpheshan/type-state-pattern-in-ballerina-6ccf3a21bbb
author_url
https://medium.com/@hpheshan
status
ok
fetched_at
2026-07-26 12:53:17