onEvent is the function used to aggregate events into state. Conceptionally it is very similar
to the function you pass to
of the sorted event log as the array, and the state as the accumulated object.
Time Travel and Keeping
The most important thing to keep in mind when implementing
onEvent is that it may be called again
and again with very similar inputs. This is due to time travel. When an event from the past
arrives, it is inserted into its proper spot in the log of all relevant events. Then, in order to
compute an updated correct state, the
onEvent aggregation is run over the event log again.
Due to this, it is very important that
onEvent is a pure function. A pure function is a function
where the output depends solely on the inputs.
The following are examples of code that is NOT pure and hence must be avoided inside
Looking at the current time via
new Date()or similar. If you need to get the time at which an event occurred, look at the
Accessing dynamic global state.
Modifying anything that is not part of the output state, for example a variable captured by the
The Inputs to
The current state of the Fish. That is, a state to which all previous events have already been
Note that it will always just be the locally known previous events that have been applied. That is the exact point of time travel: Some previous events may always be yet unknown.
The current event to apply.
A collection of various meta data tied to the event.
isLocalEvent- Whether the event was created on the same node that
onEventis currently being executed on.
tags- The tags that were attached to the event when it was emitted.
timestampMicros- Microseconds since the Epoch on the node that emitted the event, at time of emission. If the clock on that node was not set correctly, this timestamp will also be wrong.
timestampAsDate- A function that returns the
timestampMicrosconverted to a plain JS
lamport- Timestamp according to the Lamport Clock. This is only useful for debugging. Events are fed ordered by Lamport timestamp ascending.
eventId- A unique identifier for the event. Every event has exactly one
eventIdwhich is unique to it, guaranteed to not collide with any other event.
The Output of
onEvent implementation must return a value of type
S. The following are all legal:
Returning the input state, unchanged. Although note that you should not ignore events.
Modifying the input state and returning it.
Returning a completely new object.
The returned value will then be fed as input
state to the
onEvent invocation for the next
It will also be potentially published to observers that have called
pond.observe for this
Fish. It’s important to note, however, that during time travel, observers are not notified of
intermediate states – they are only notified of the updated new latest state.
A similar thing happens when starting observation on a new Fish that already has some events: All existing events are applied, and then the observer receives the latest state. No intermediate states are passed to the observation callback.
ActyxOS uses a specialized mechanism called Lamport Clock to sort events. Effectively this works like sorting by time, only better. Sorting by wall clock time would run into issues when clocks on devices are off: It would require a robust NTP setup to keep time in sync.
Lamport timestamps work independently from device time. They also do a good job of preserving a useful, logical order even when network partitions happen.
In the rare case that lamport timestamp of two events should be identical, other factors are used to
decide on a consistent ordering. Ultimately, the ordering of
what decides the ordering of events.