Skip to content

Instantly share code, notes, and snippets.

@JogoShugh
Last active September 13, 2022 11:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JogoShugh/269515a4da37aec584d436b754739040 to your computer and use it in GitHub Desktop.
Save JogoShugh/269515a4da37aec584d436b754739040 to your computer and use it in GitHub Desktop.
Stateful vs Eventful

Calculating current state

When storing events to track the complete path of state transitions for an aggregate, it is always possible to also store a projection of the current state, but this is usually only necessary for aggregates that have large numbers of events. Since each SCA Transaction will not have a large number of events, that is probably overkill. Instead, a simple process of reading the list of past events and flowing them through a function that is capable of producing, or projecting, a model is rather straight forward. The most important part is to design this such that it only operates on the events, not on the original requests of "commands" since these often have side-effects like calling APIs, saving data, etc.

In Kotlin, this is easily achieved with a left fold over the set of events. For illustration purposes, consider the following simple illustration. You can run and edit the sample online here in the Kotlin Playground.

class StatefulCalculator()
{
    var total = 0
    
    fun Add(value : Int = 1) : StatefulCalculator {
        total = total + value
        return this
    }
    fun Subtract(value : Int = 1) : StatefulCalculator {
        total = total - value
        return this
    }
    fun Multiply(value : Int = 2) : StatefulCalculator {
        total = total * value
        return this
    }
}


open class Operation(val value : Int = 1) {
    override fun toString() : String = "${this.javaClass.name}(${value})"
}
class Added(value : Int = 1) : Operation(value)
class Subtracted(value : Int = 1) : Operation(value)
class Multiplied(value : Int = 2) : Operation(value)

class EventfulCalculator() {
    
    private var events = mutableListOf<Operation>()
    
    fun Add(value : Int = 1) : EventfulCalculator {
        events.add(Added(value))
        return this
    }
    
    fun Subtract(value : Int = 1) : EventfulCalculator {
        events.add(Subtracted(value))
        return this
    }
    
    fun Multiply(value : Int = 2) : EventfulCalculator {
        events.add(Multiplied(value))
        return this
    }
        
    val total get() = events.fold(0) { total, item ->
    		when(item) {
                is Added -> total + item.value
                is Subtracted -> total - item.value
                is Multiplied -> total * item.value
                else -> total
    		}
        }  
    
    val eventsList get() = events
}

fun main() {
    
    val statefulCalc = StatefulCalculator()
    statefulCalc
        .Add()
    	.Subtract()
        .Add()
        .Add()
        .Add()
        .Multiply()
        .Subtract()
    println("statefulCalc total: ${statefulCalc.total}")  
   
    val eventfulCalc1 = EventfulCalculator()
    eventfulCalc1
        .Add()
    	.Subtract()
        .Add()
        .Add()
        .Add()
        .Multiply()
        .Subtract()
    println("eventfulCalc1 total: ${eventfulCalc1.total}")
    
    val eventfulCalc2 = EventfulCalculator()
    eventfulCalc2
    	.Add()
        .Multiply(4)
        .Add()
    println("eventfulCalc2 total: ${eventfulCalc2.total}")
    
    println("eventCalc1 events: ${eventfulCalc1.eventsList}")
    println("eventCalc2 events: ${eventfulCalc2.eventsList}")
}

Output

statefulCalc total: 5
eventfulCalc1 total: 5
eventfulCalc2 total: 5
eventCalc1 events: [Added(1), Subtracted(1), Added(1), Added(1), Added(1), Multiplied(2), Subtracted(1)]
eventCalc2 events: [Added(1), Multiplied(4), Added(1)]

Observations

  • In this code, we have both a StatefulCalculator and an EventfulCalculator.
  • The StatefulCalculator keeps a simple integer value and mutates it with every method call, which results in a loss of information about how the value actually ended up the way it is.
  • The EventfulCalculator keeps a list of all "events" and folds over them when the total property is accessed to produce the "current state".
    • Important: events are all named in the past tense because these are facts about things that took place.
  • The public calls to each type of calculator are the same.
  • Note that even though all instances of the calculators produce the value 5, only the StatefulCalculator instances are capable of showing us the radically different way that the value was produced.

Obviously, events in the SCA domain will be much more information-rich than in this simple example, but the concepts are identical for applying events to produce current state or any other imagined read model.

More complex state keeping, timeline generation, and point-in-time queries

As a quick demonstration of a more advanced StatefulCalculator, let's get rid of the other one and add a createDate field along with a way to show both a timeline of the events and to query for that the total was at any step along the way to the completed total. This version also introduces a bit more OOP by getting rid of the when statement in favor of the Operation class having an abstract operate method.:

import java.util.Date
import java.text.SimpleDateFormat
import kotlin.math.roundToInt

abstract class Operation(val value : Int = 1, val createDate : Date = Date()) {
    override fun toString() : String = "${this.javaClass.name}(${value}@${formatter.format(createDate)})"
    
    abstract fun operate(currentTotal: Int) : Int
    
    private val formatter = SimpleDateFormat("HH:mm:ss:SSS")
}

class Added(value : Int = 1) : Operation(value) {
    override fun operate(currentTotal: Int) = currentTotal + value
}

class Subtracted(value : Int = 1) : Operation(value) {
    override fun operate(currentTotal: Int) = currentTotal - value
}

class Multiplied(value : Int = 2) : Operation(value) {
	override fun operate(currentTotal: Int) = currentTotal * value    
}

class EventfulCalculator() {
    
    private var events = mutableListOf<Operation>()
    
    fun Add(value : Int = 1) : EventfulCalculator {
        delay()
        events.add(Added(value))
        return this
    }
    
    fun Subtract(value : Int = 1) : EventfulCalculator {
        delay()
        events.add(Subtracted(value))
        return this
    }
    
    fun Multiply(value : Int = 2) : EventfulCalculator {
        delay()
        events.add(Multiplied(value))
        return this
    }
        
    val total get() = calculateTotal(events)

    fun totalAsOf(operationNumber : Int = 1) = calculateTotal(events.take(operationNumber))
    
    private fun calculateTotal(ops : List<Operation>) = ops.fold(0) { 
        currentTotal, item -> item.operate(currentTotal)
    }
    
    val eventsList get() = events
    
    private fun delay() = Thread.sleep((Math.random() * 250).roundToInt().toLong())
}

fun main() {
           
    val eventfulCalc1 = EventfulCalculator()
    eventfulCalc1
        .Add()
    	.Subtract()
        .Add()
        .Add()
        .Add()
        .Multiply()
        .Subtract()
    println("eventfulCalc1 total: ${eventfulCalc1.total}")
    println("eventfulCalc1 events: ${eventfulCalc1.eventsList}")
    
    println("eventulCalc1 history:")
    println("eventfulCalc1 total as of 1: ${eventfulCalc1.totalAsOf(1)}")
    println("eventfulCalc1 total as of 2: ${eventfulCalc1.totalAsOf(2)}")
    println("eventfulCalc1 total as of 3: ${eventfulCalc1.totalAsOf(3)}")
    println("eventfulCalc1 total as of 4: ${eventfulCalc1.totalAsOf(4)}")
    println("eventfulCalc1 total as of 5: ${eventfulCalc1.totalAsOf(5)}")
    println("eventfulCalc1 total as of 6: ${eventfulCalc1.totalAsOf(6)}")
    println("eventfulCalc1 total as of 7: ${eventfulCalc1.totalAsOf(7)}")    
}

Output

eventfulCalc1 total: 5
eventfulCalc1 events: [
    Added(1@11:11:31:285), 
    Subtracted(1@11:11:31:407),
    Added(1@11:11:31:471),
    Added(1@11:11:31:519),
    Added(1@11:11:31:620),
    Multiplied(2@11:11:31:754),
    Subtracted(1@11:11:31:866)
]
eventulCalc1 history:
eventfulCalc1 total as of 1: 1
eventfulCalc1 total as of 2: 0
eventfulCalc1 total as of 3: 1
eventfulCalc1 total as of 4: 2
eventfulCalc1 total as of 5: 3
eventfulCalc1 total as of 6: 6
eventfulCalc1 total as of 7: 5

Observations

  • In this version, the StatefulCalculator generates a timestamp every time it stores an event. It also adds an artificial delay to simulate real-world user interactions which have natural variations.
  • The output shows us the complete series of events along with the timestamp.
  • And, it shows us the total at any step by virtue of the new and simple method fun totalAsOf(operationNumber : Int = 1) = calculateTotal(events.take(operationNumber)).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment