Overview

Eventuate is a toolkit for building applications composed of event-driven and event-sourced services that communicate via causally ordered event streams. Services can either be co-located on a single node or distributed up to global scale. Services can also be replicated with causal consistency and remain available for writes during network partitions. Eventuate has a Java and Scala API, is written in Scala and built on top of Akka, a toolkit for building highly concurrent, distributed, and resilient message-driven applications on the JVM.

Event sourcing

Event sourcing captures all changes to application state as a sequence of events. These events are persisted in an event log and can be replayed to recover application state. Events are immutable facts that are only ever appended to an event log which allows for very high transaction rates and efficient replication. Eventuate provides the following event-sourcing abstractions which can be extended by applications:

  • Event-sourced actor. An Akka actor that consumes events from an event log and produces events to the same event log. Internal state derived from consumed events is an in-memory write model contributing to the command-side (C) of a CQRS-based application.
  • Event-sourced view. An Akka actor that only consumes events from an event log. Internal state derived from consumed events is an in-memory read model contributing to the query-side (Q) of CQRS.
  • Event-sourced writer. An Akka actor that consumes events from an event log to update a persistent query database. State derived from consumed events is a persistent read model contributing to the query-side (Q) of CQRS.
  • Event-sourced processor. An Akka actor that consumes events from one event log and produces processed events to another event log. Processors can be used to connect event logs to event processing pipelines or graphs.

Implementations of these abstractions are referred to as event-sourced components. Applications compose them to event-sourced services and make them accessible via application-level protocols[1].

Event collaboration

Events produced by one event-sourced component can be consumed by other event-sourced components if they share a local or distributed event log. This allows them to communicate via events a.k.a. event collaboration. Event collaboration use cases include:

  • Distributed business processes. Event-sourced actors of different type communicate via events to achieve a common goal. They play different roles in a business process and react on received events by updating application state and producing new events. We refer to this form of event collaboration as Event-driven communication.
  • Actor state replication. Event-sourced actors of same type consume each other’s events to replicate internal state with causal consistency. Eventuate allows concurrent updates to replicated actor state and supports automated and interactive conflict resolution in case of conflicting updates.
  • Event aggregation. Event-sourced views and writers aggregate events from other event-sourced components to generate application-specific views.

Event collaboration is reliable. For example, a distributed business process that fails due to a network partition automatically resumes when the partition heals.

Event log

An Eventuate Event log can be operated at a single location or replicated across multiple locations. A location is an availability zone that accepts writes to a Local event log even if it is partitioned from other locations. Local event logs from multiple locations can be connected to a Replicated event log that preserves causal event ordering.

Locations can be geographically distinct locations, nodes within a cluster or even processes on the same node, depending on the granularity of availability zones needed by an application. Event-sourced actors and processors always write to their local event log. Event-sourced components can either collaborate over a local event log at the same location or over a replicated event log at different locations.

Local event logs have pluggable storage backends. At the moment, Eventuate provides plugins for a Cassandra storage backend and a LevelDB storage backend. The Cassandra plugin writes events to a Cassandra cluster and should be used if stronger durability guarantees are needed. The LevelDB storage plugin writes events to a LevelDB instance on the local filesystem and should be used if weaker durability guarantees are acceptable or a lightweight storage backend is needed.

Storage backends from different locations do not directly communicate with each other. Asynchronous event replication across locations is Eventuate-specific and also works between locations with different storage backends. Synchronous event replication within a storage backend at a given location is optional and only used to achieve stronger durability.

Event bus

Event-sourced components have a subscription at their event log. Newly written events are pushed to subscribers which allows them to update application state with minimal latency. An event written at one location is reliably pushed to subscribers at that location and to subscribers at remote locations. Consequently, event-sourced components that exchange events via a replicated event log communicate over a federated, durable and partition-tolerant event bus that preserves causal event ordering. During inter-location network partitions services can continue to write events locally. Delivery of events to remote locations automatically resumes when the partition heals.

Event ordering and consistency

The delivery order of events during push updates (see Event bus) is identical to that during later event replays because the delivery order of events to event-sourced components is determined by local event storage order. Within a location, all event-sourced components see the same order of events. The delivery and storage order of replicated events at distinct locations is consistent with causal order: causally related events have the same order at all locations whereas concurrent events may have different order. This is important to achieve causal consistency which is the strongest possible consistency for applications that choose AP of CAP i.e. applications that should remain available for writes during network partitions[2]. In Eventuate, causality is tracked as potential causality with Vector clocks.

Applications that favor strong consistency (CP of CAP) of actor state on the command-side should consider single-location deployments with event-sourced actor singletons. From a high-level perspective, single-location Eventuate applications share many similarities with Akka Persistence applications[3][4]. In this context, Eventuate can be regarded as functional superset of Akka Persistence with additional support for

  • actor state replication by relaxing strong consistency to causal consistency
  • event aggregation from multiple producers that preserves causal event ordering and
  • event collaboration with stronger ordering guarantees than provided by plain reliable messaging[5].

Operation-based CRDTs

Eventuate comes with an implementation of Operation-based CRDTs (commutative replicated data types or CmRDTs) as specified in A comprehensive study of Convergent and Commutative Replicated Data Types. In contrast to state-based CRDTs (convergent replicated data types or CvRDTs), operation-based CRDTs require a reliable broadcast channel with causal delivery order for communicating update operations among replicas. Exactly these properties are provided by an Eventuate event bus so it was straightforward to implement operation-based CRDTs on top of it. Operations are persisted as events and delivered to replicas over the event bus. The state of operation-based CRDTs can be recovered by replaying these events, optionally starting from a state snapshot.

Stream processing adapters

Although Eventuate can be used to build distributed stream processing applications, it doesn’t aim to compete with existing, more elaborate stream processing frameworks such as Spark Streaming or Akka Streams, for example. Eventuate rather provides Adapters to these frameworks so that events produced by Eventuate applications can be further analyzed there and results written back to Eventuate event logs.