Skip to content
Event sourced state machines
Branch: develop
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.circleci Release/1.13.0 (#54) Mar 6, 2019
.globality
build.python-library/docker-base Generating build files through globality-build Dec 18, 2018
microcosm_eventsource
.ackrc
.bumpversion.cfg Bump Apr 22, 2019
.dockerignore
.gitignore
CONTRIBUTING.md
Dockerfile.template Generating build files through globality-build Dec 18, 2018
LICENSE
MANIFEST.in
README.md
entrypoint.sh Release/1.13.0 (#54) Mar 6, 2019
setup.cfg Release/1.13.0 (#54) Mar 6, 2019
setup.py
tox.ini

README.md

microcosm-eventsource

Event-sourced state machines using microcosm.

Manages state changes as an immutable event log using:

By Example

Imagine a task list with the following rules:

  • A task is created with a text description.

  • After a task is created, it can be assigned to an assignee and scheduled for a deadline. These operations can happen in any order.

  • After a task is both assigned and scheduled, it may be started.

  • After a task is started, it may be reassigned or rescheduled any number of times.

  • After a task is started, it may either be canceled or completed. Both of these states are terminal.

  • Before a task is canceled or completed, it maybe revised at any time. A revised task reverts back to the initial state.

These actions collectively define a state machine, where each transition is triggered by a new event:

          => ASSIGNED => SCHEDULED =>             => CANCELED
        /                             \         /
CREATED                                 STARTED
        \                             /         \
          => SCHEDULED => ASSIGNED =>             => COMPLETED

(For simplicity, the REASSIGNED, RESCHEDULED, and REVISED events are not shown.)

Defining Tasks

Every task will be defined in a task table, which needs only the default, auto-generated primary key and timestamp fields:

class Task(UnixTimestampEntityMixin, Model):
    __tablename__ = "task"


@binding("task_store")
class TaskStore(Store):

    def __init__(self, graph):
        super(TaskStore, self).__init__(graph, Task)

Defining Task Events

Every action that changes states will be defined in a task_event table, which has:

  • a foreign key reference back to the "container" task table
  • a reference to an event type enumeration (see below)
  • zero or more additional columns

For example:

@add_metaclass(EventMeta)
class TaskEvent(UnixTimestampEntityMixin):
    __tablename__ = "task_event"
    __eventtype__ = TaskEventType
    __container__ = Task

    assignee = Column(String)
    deadline = Column(DateTime)

@binding("task_event_store")
class TaskEventStore(EventStore):

    def __init__(self, graph):
        super(TaskEventStore, self).__init__(graph, TaskEvent)

Defining Task Event Types

It remains to define the legal events and their transitions using an enumeration. This is done as follows:

  • Each enumerated value is identified using its (left hand side) name.
  • Each enumerated value is configured using its (right hand side) meta data.
  • This meta data includes:
    • "following conditions" for legal state transitions.
    • a flag controlling whether state accumulates through this transition
    • a flag controlling whether the state machine is restarted (with a new version) after this state
    • a list of zero or more required additional columns for this state

Example:

class TaskEventType(EventType):
    CREATED = event_info()
    ASSIGNED = event_info(
        follows=all_of("CREATED", but_not("ASSIGNED")),
        accumulate=union(),
        requires=["assignee"],
    )
    SCHEDULED = event_info(
        follows=all_of("CREATED", but_not("SCHEDULED")),
        accumulate=union(),
        requires=["deadline"],
    )
    STARTED = event_info(
        follows=all_of("ASSIGNED", "SCHEDULED"),
    )
    REASSIGNED = event_info(
        follows=event("STARTED"),
        accumulate=keep(),
        requires=["assignee"],
    )
    RESCHEDULED = event_info(
        follows=event("STARTED"),
        accumulate=keep(),
        requires=["deadline"],
    )
    REVISED = event_info(
        follows=any_of("CREATED", "STARTED"),
        restarting=True,
    )
    CANCELED = event_info(
        follows=event("STARTED"),
    )
    COMPLETED = event_info(
        follows=event("STARTED"),
    )

Running tests

First, create role and DB:

createuser microcosm_eventsource
createdb -O microcosm_eventsource microcosm_eventsource_test_db

Second, install nose:

pip install nose pyhamcrest

Then, run nosetests

You can’t perform that action at this time.