null theorem

I code things.

Observable Models

2019-04-13

Recently I’ve been trying out a form of domain modeling in Unity which is inspired by the Event Sourcing pattern, and in particular the principle of the events being the source of truth for the model. By disallowing direct querying of the model’s state, and instead only permitting read dependencies on a stream of events, the model’s internal data structure can be isolated and evolved independently from its contracts. The separation of model and presentation concerns greatly helps with preventing your game’s code from turning into a difficult to change ball of mud, and further separating the model’s internal data structure from its contract allows the internal details of the model to evolve without affecting the presentation code, which makes change easier and less prone to introducing bugs.

What does that mean in practice? That the only readable property of the model is an event stream. In addition to the event stream, the model contains a number of command methods, which to keep things simple I’ve just been sticking to void methods receiving value (or immutable value object) parameters. It’s basically Command-query separation where the query side of the model is an event stream.

For example, consider the contract of a simple model of an incrementing counter: the model accepts an Increment() command, and emits an IncrementedEvent containing the new value of the counter. This can be represented in C# as an interface and some classes to represent the events.

using System;

// The model's contract
public interface ICounter 
{
    // Just one public property: the event stream
    IObservable<CounterEvent> Events { get; }

    // One or more command methods
    void Increment();
}

// Base class for Counter events
public abstract class CounterEvent { }

// Immutable event class
public class IncrementedEvent : CounterEvent
{
    public int NewCount { get; }

    public IncrementedEvent(int newCount)
    {
        NewCount = newCount;
    }
}

Presumably, an implementation of the ICounter interface would need to include an int state variable to keep track of the current value of the counter, but that detail is kept hidden away from any other object which depends on the counter model. This might not seem very important when the structure of the state is simply an integer, but when dealing with data structures of greater complexity it can be extremely valuable to be able to change the internal structure of a model as it evolves to meet new requirements. For instance, changing the indexing and the way data is split up amongs various data structures can improve performance or make algorithms simpler to implement, however when these structures are exposed publicly then changes to them will require all dependencies to change as well. When the structure of the internal data is kept private and only events are exposed publicly, then that structure is free to change without effecting external dependencies, so long as events are still emitted when they are expected.

I’ve been using UniRx for my reactive needs in Unity, which makes implementing the IObservable<CounterEvent> part of the contract quite simple (I also appreciate that it’s on the Asset Store, which makes it really convenient to import into projects). All that’s required is a private Subject<CounterEvent> which fulfills both IObservable<CounterEvent> and IObserver<CounterEvent>; when OnNext(...) is called on the subject with an event, it will be sent to all of the observers subscribed to the event stream.

using System;
using UniRx;

public class Counter : ICounter
{
    // Internal subject for the event stream
    private Subject<CounterEvent> _events = new Subject<CounterEvent>();

    // The model's internal state
    private int _count;

    // Just exposing the IObservable side of the subject publicly
    public IObservable<CounterEvent> Events => _events;

    // Implementation of the command
    public void Increment()
    {
        // Mutate state
        _count++;

        // Emit event
        _events.OnNext(new IncrementedEvent(_count));
    }
}

This kind of model can then be integrated with Unity by referencing it from MonoBehaviour scripts attached to GameObjects in the scene hierarchy, which then either react to user input or other events in the scene and call commands on the model, or react to events in the model and cause changes in behaviour on components on the GameObject. I personally prefer to use the Zenject IoC container (also on the Asset Store) to connect the models to the MonoBehaviours in my Unity scenes.

using UnityEngine;
using UnityEngine.UI;
using UniRx;
using Zenject;

// Presenter for a Text field containing the current count
public class CountTextPresenter : MonoBehaviour
{
    // Reference to the Text component
    public Text CountText;

    [Inject] // Method injection from Zenject
    public void Initialize(ICounter counterModel)
    {
        // Filter out IncrementedEvents and update text on those events
        counterModel.Events
            .OfType<CounterEvent, IncrementedEvent>()
            .Subscribe(e => CountText.text = e.NewCount.ToString());
    }
}
using UnityEngine;
using UnityEngine.UI;
using UniRx;
using Zenject;

// Presenter for a Button to increment the count
public class IncrementButtonPresenter : MonoBehaviour
{
    // Reference to the Button component
    public Button IncrementButton;

    [Inject] // Method injection from Zenject
    public void Initialize(ICounter counterModel)
    {
        // Call Increment() when Button is clicked
        IncrementButton.onClick.AddListener(() => counterModel.Increment());
    }
}
using Zenject;

// Zenject Installer added to Scene Context
public class SceneInstaller : MonoInstaller
{
    public override void InstallBindings()
    {
        // Bind the Counter model as a singleton
        Container.Bind<ICounter>().To<Counter>().AsSingle();
    }
}

If these MonoBehaviours can be kept simple and just act as the glue between the model and Unity components rather than containing complicated state and logic, then complexity can be evolved in the model where it’s easy to test and change it in isolation from the complexity of Unity. By cleanly separating the abstract game logic of the model from the details of presenting the game in Unity through well defined event-based contracts that don’t leak the model’s data structure, these concerns can be changed independently without requiring a change to presentation concerns. Making the most common changes easy is one the fundamental goals of architecture, and observable models are a further refinement on the architectural decision to separate domain logic from presentation.

As with event sourcing in general, this pattern is not without its challenges. Not being able to directly query the state of the model requires a change in thinking about dependencies, and deciding on the separation between presentation behaviour and doman logic in the context of a game is not always straightforward. However if you’ve been running up against the problems of tight coupling between your presentation components or a difficult to change domain model, then you might find some value in the observable model pattern.


If you’re interested in learning more about using the Zenject container to inject models into presentation components, check out this tutorial I made on Dependency Injection in Unity with Zenject. It involves code that’s quite similar to the stuff shown here, except that it uses basic C# events instead of the reactive abstractions IObservable<T> and IObserver<T>.