• This repository has been archived on 27/Apr/2023
  • Stars
    star
    118
  • Rank 299,923 (Top 6 %)
  • Language
    Scala
  • Created about 9 years ago
  • Updated over 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Example for "Entry level event-sourcing" blog

slick-eventsourcing is a library/framework which implements a specific approach to event sourcing, using a relational database. See this blog for an introduction.

Using slick-eventsourcing you can write applications which take advantage of transactional database features such as consistency and SQL query capabilities and in addition contain (or, in fact, are driven by) a full audit log of all actions in the system.

Build Status Join the chat at https://gitter.im/softwaremill/slick-eventsourcing Maven Central Dependencies

Example

If you'd like to see a simple application written using the framework, see the example directory in the sources. You can run it with ./example-start.sh and then pointing you web browser to localhost:8080. Observe the console output for events that are created and handled.

Model

Events are the main source of truth in the system and contain information on what happened (past tense!) in the system. Events are usually created in response to user actions, but can also be created in response to other events, or external triggers in the system.

Basing on events, a read model is created. The read model is used to validate user actions before creating new events and to provide data to user queries. The model consists of ordinary SQL tables. The main difference to a traditional "CRUD" application is that updates occur only basing on events, not directly as a result to user actions.

There are three main types of functions involved:

  • model update functions: take an event and update the read model accordingly.
  • event listeners: take an event and run business logic. Can create other events, which are handled recursively.
  • commands: take user input, validate it and create events.

Model update functions can be used to re-create the read model if needed (or create an updated read model), hence they shouldn't have any side-effects other than updating the model.

Event listeners are supposed to be run only when the event is first handled, hence that's where side-effects should occur.

Commands

The main way events are created is through commands. A command is a plain Scala function which accepts some input data and returns a result consisting of: a success or failure value (represented as a Either[F, S]) and a list of emitted events (List[PartialEvent[_, _]; partial, as not all event metadata is provided upfront; e.g. event and transaction ids are filled in automatically later). As commands can use the read model to validate the input, everything is wrapped in a DBIOAction. Hence a CommandResult is a:

type CommandResult[F, S] = DBIOAction[(Either[F, S], List[PartialEvent[_, _]]), NoStream, Read]

Note that when creating a command result, we are returning a description (DBIOAction) of how the database should be queried; the real queries will be invoked only when the action is run. Such descriptions are ordinary immutable Scala values, can be re-used and shared multiple times.

Model update & event listener functions

The model update and event listener functions have similar signatures:

type EventListener[T] = Event[T] => DBIOAction[List[PartialEvent[_, _]], NoStream, Read]
type ModelUpdate[T] = Event[T] => DBIOAction[Unit, NoStream, Read with Write]

Model update doesn't return any values, and can read & write the model. Event listeners can only read, but return a list of partial events. Both results are wrapped, as with CommandResult, in a DBIOAction, so they return a description of what should be done, and that is only run later.

If you would like to run some side-effects in an event listener, e.g. communicating over the network with an external service, you can wrap a Future[T] into a DBIOAction[T] with the DBIO.from method.

Registry

You need to specify what kind of model update and event listeners you would like to be run in response to events. This is done through a Registry, which is an immutable class and contains three builder methods:

  • registerModelUpdate(h: ModelUpdate[T])
  • registerEventListener(h: EventListener[T])
  • registerAsyncEventListener(h: EventListener[T])

The difference between regular (synchronous) and asynchronous event listeners will be discussed later.

You can also specify the default org.json4s.Formats to use to serialize/deserialize event data, and define event-specific formats using the registerFormats[T](formats) method (where T is the type of the event).

Event machine

Given a registry, it is possible to create an EventMachine. The event machine is responsible for running appropriate actions when events are created. The main method in the event machine is:

def run[F, S](cr: CommandResult[F, S]): Future[Either[F, S]]

This method takes a command result, runs the actions that are described by it using the database and returns the result to the user. Any events that are created are first persisted in the database, and then handled by first running all appropriate model update functions, and then event listeners. If the event listeners create more events (e.g. by returning the result of running other commands), they are handled recursively (by first persisting them, running model update, then event listeners, etc.)

Transactions

What's very important for data consistency is that the database actions needed to compute the command result, persist the events, run the model update functions and event listeners are all done in a single transaction. So if at any point there is a failure, either due to an exception in the business logic, or if a constraint in the database fails, nothing will be persisted.

The exception here are asynchronous event listeners (registered with registerAsyncEventListener), which are run after the transaction completes, in the background. Such event listeners are useful if some logic needs to be run when an event is created, but without blocking the user from receiving a reply or the transaction from committing. If an asynchronous event listener creates more events, they are handled in a single transaction with the original event (so there's one additional transaction for each event and asynchronous event listener)

How to create events

An event consist of arbitrary event data and some meta-data. The event data is usually a case class, and is serialised to JSON when written to the database. Each event is associated with an aggregate (a "root entity") through an id. To define what's the aggregate for an event, an implicit AggregateForEvent[T, U] must be available (typically defined in the companion object for the event's data), where T is the type of the event data and U is type of the aggregate.

To create an event you should use the Event(eventData) method, which takes an implicit AggregateForEvent. Then, you need to specify what's the id of the aggregate, either by using forAggregate, or forNewAggregate, if the id is not yet available.

That way you will obtain a PartialEvent which can be returned from a command or an event listener.

Before persisting, other meta-data will be generated for the event:

  • id
  • eventType: corresponds to the class of event data
  • aggregateType: name of the aggregate
  • aggregateId
  • isNew: a flag if the aggregate is new, or is the event modifying an existing one
  • created: timestamp for the event
  • userId: the id of the user which created the event (if any)
  • txId: id of the transaction, all events created within a single transaction will have the same id

Ids

Aggregate and event ids are created by an IdGenerator, which is needed when creating the EventMachine. The id generator generates time-based unique Long ids. If there are multiple nodes, each IdGenerator should be created with a different workerId.

The ids are type-tagged, so that at runtime they are simple Longs, however at compile-time they are type-safe and bound to a specific aggregate/entity. For example Long @@ User is a long value tagged with the User type; it cannot be used e.g. where a Long @@ Product is expected. To tag a value, you can use the .taggedWith[T] method.

To properly type-tag methods which return or expect a user id (e.g. to invoke Event.userId), an implicit instance of UserType[T] should be in scope. There should be only one instance of that type in the whole system, and it should be parametrised by the type of the user entity.

Handle context

When invoking EventMachine.run, an implicit HandleContext is needed. The handle context contains the id of the currently logged in user, so that it can be associated with events that are created. If no user is logged in, HandleContext.System should be used. Otherwise, before invoking run, declare an implicit handle context using the one-arg constructor.

As each system will have its own notion of a user, the user type needs to be provided by an implicit UserType[T] value. There should only be a single, implicit instance of UserType, parametrised by the actual type of the user aggregate. That implicit is needed when obtaining the id of the user who created the event (to provide the appropriate type tag), and when creating a handle context.

If an event causes a user to be logged in (e.g. a user registered or user logged in event), it should implement the HandleContextTransform method, which should add the user id to the context.

Using from SBT

libraryDependencies += "com.softwaremill.events" %% "core" % "0.1.5"

How to recover model state

In case you lost your database model but got event log, you can rebuild state from it. In example project, there is a manual tool called RecoverDbState which shows how to call eventMachine's recover methods:

  • recoverStoredEvents - reply all events stored in event log until given (timeLimit) point in time.
  • recoverSingleStoredEvent - only one event by id
  • recoverStoredEventsRange - recovers all events from given range.

Version history

  • 25/11/2015, 0.1: initial release
  • 27/11/2015, 0.1.1: bug fix
  • 30/11/2015, 0.1.2: updating to akka-http 2.0-m2
  • 7/12/2015, 0.1.3: making EventStore a trait, changing param type in EventsDatabase
  • 15/12/2015, 0.1.4: initial recovery support
  • 21/12/2015, 0.1.5: registering custom event formats, updating to akka-http 2.0
  • 13/01/2016, 0.1.6: recovery fix, event json deserialization error doesn't stop processing

More Repositories

1

elasticmq

In-memory message queue with an Amazon SQS-compatible interface. Runs stand-alone or embedded.
Scala
2,502
star
2

sttp

The Scala HTTP client you always wanted!
Scala
1,454
star
3

tapir

Rapid development of self-documenting APIs
Scala
1,363
star
4

macwire

Zero-cost, compile-time, type-safe dependency injection library.
Scala
1,266
star
5

quicklens

Modify deeply nested case class fields
Scala
822
star
6

magnolia

Easy, fast, transparent generic derivation of typeclass instances
Scala
765
star
7

bootzooka

Simple project to quickly start developing a Scala-based microservice or web application, without the need to write login, user registration etc.
Scala
695
star
8

codebrag

Your daily code review tool
Scala
651
star
9

akka-http-session

Web & mobile client-side akka-http sessions, with optional JWT support
Scala
440
star
10

ox

Safe direct-style concurrency and resiliency for Scala on the JVM
Scala
382
star
11

it-cfp-list

List of Call For Papers for IT conferences
374
star
12

retry

because you should never give up, at least not on the first try
Scala
351
star
13

diffx

Pretty diffs for scala case classes
Scala
344
star
14

kmq

Kafka-based message queue
Scala
330
star
15

scala-clippy

Good advice for Scala compiler errors
Scala
315
star
16

supler

Rapid Form Development library. Use your favourite JS frontend & Scala backend frameworks.
Scala
286
star
17

jox

Fast and Scalable Channels in Java
Java
248
star
18

mqperf

Scala
144
star
19

scala-common

Tiny independent libraries with a single purpose, often a single class
Scala
120
star
20

lemon-dataset

Lemons quality control dataset
103
star
21

maven-badges

A node.js implementation of https://github.com/jirutka/maven-badges, originally created in ruby.
TypeScript
86
star
22

sbt-softwaremill

A sane set of default build settings
Scala
72
star
23

akka-vs-scalaz

Scala
63
star
24

recursion-training

Recursion schemes training examples and exercises
HTML
59
star
25

livestub

The HTTP server stub you always wanted!
Scala
53
star
26

scala-sql-compare

Scala
50
star
27

stringmask

A micro-library for macro-based case class field masking in .toString
Scala
48
star
28

scala-id-generator

Scala
48
star
29

confluent-playground

Java
44
star
30

sttp-model

Simple Scala HTTP model
Scala
43
star
31

odelay

delayed reactions
Scala
42
star
32

saft

Scala
42
star
33

sttp-openai

Scala
41
star
34

akka-simple-cluster-k8s

Scala
39
star
35

walk-that-type

A tool for evaluating TypeScript types step by step.
TypeScript
39
star
36

softwaremill-common

SoftwareMill Common library
Java
37
star
37

OtterJet

Visualization of messages from a NATS JetStream server
Java
33
star
38

scala-pre-commit-hooks

Pre-commit/Pre-push hooks for Scala
Python
32
star
39

FoXAI

The open-source library for explainable AI. Generic and easy to integrate with PyTorch.
Python
31
star
40

neme-plugin

Scala compiler plugin for turning non exhaustive match warnings into errors
Scala
29
star
41

reactive-event-sourcing-java

Java
28
star
42

zio2-structure

Scala
26
star
43

helisa

Scala API for jenetics
Scala
26
star
44

streams-tests

Scala
25
star
45

meerkat

Observability Starter Kit for JVM Applications
JavaScript
25
star
46

tapir-loom

Scala
24
star
47

sttp-apispec

OpenAPI, AsyncAPI and JSON Schema Scala models.
Scala
23
star
48

node-typescript-starter

A basic boilerplate for node + TypeScript development with debugger source maps support.
TypeScript
22
star
49

free-tagless-compare

Free monads compared to tagless final
Scala
22
star
50

akka-http-session-faq

Java
21
star
51

activator-reactive-kafka-scala

Activator template for Reactive Kafka
Scala
20
star
52

adopt-tapir

A quickstart generator for Tapir projects
Scala
17
star
53

reactive-streams-for-java-developers

Java
17
star
54

scala3-macro-debug

Scala
17
star
55

resilience4s

Scala
16
star
56

react-use-promise-matcher

React hooks allowing you to handle promises in a stateful way
TypeScript
16
star
57

simple-http-server

Simple JVM based HTTP server with no dependencies
Scala
15
star
58

correlator

Scala
15
star
59

detectnet-tests

Python scripts and other resources for tesing DetectNet on Nvidia DIGITS
Python
14
star
60

blockchain-schedule

An experimental collaborative planning app based on Ethereum ("Decentralized Doodle")
TypeScript
14
star
61

blog-scala-structure-lifecycle

Scala
12
star
62

akka-sandbox

Training ground for experiments with Akka framework.
Scala
12
star
63

broadway-pipelines-blog

Constructing effective data processing workflows using Elixir and Broadway
Elixir
12
star
64

undelay

Satisfy Scala Futures quickly
Scala
11
star
65

monix-correlation-id

Scala
10
star
66

cassandra-monitoring

Scripts for the Cassandra Monitoring blog miniseries
10
star
67

reason-companies-example

Reason example application
OCaml
10
star
68

jvmbot

Scala
9
star
69

botarium

A simple starter kit for building bots using Node + TypeScript + BotKit.
TypeScript
8
star
70

sbt-template

Scala
8
star
71

asamal

POC for a CDI-based web lightweight framework
Java
8
star
72

boot-scala-microservice

Bootstrap microservice template that uses micro-deps library https://github.com/4finance/micro-deps
Scala
8
star
73

sttp-shared

Scala
7
star
74

modem-connector

Modulator and Demodulator for HAM Radio AX.25 audio signals
Scala
7
star
75

vehicle-routing-problem-java

Java
6
star
76

idea-pastie-plugin

Plugin to post pastie.org pasties from IntelliJ Idea
Java
5
star
77

trqbox-demo

Ruby
5
star
78

sentinel-cgan

Sentinel generative conditional adversarial network implementation
Python
5
star
79

scalatimes

Pug
5
star
80

tapir-serverless

Scala
5
star
81

scala-compiler-plugin-template

Scala
5
star
82

gatling-zeromq

A Gatling stress test plugin for ZeroMQ protocol
Scala
5
star
83

slack-alphabet

Scala
4
star
84

scalar-conf-website

Scalar - Scala Conference in Central Europe
Python
4
star
85

try-them-off

Showcase service presenting possible usage of the Try monad from Vavr.
Java
4
star
86

sttp-openapi-example

Scala
4
star
87

cache-get-or-create

Java
4
star
88

bootzooka-react

Simple project to quickly start developing a web application using React and Akka HTTP, without the need to write login, user registration etc. https://softwaremill.com/open-source/
Scala
4
star
89

fabrica

Shell
3
star
90

akka-typed-workshop

Scala
3
star
91

kuberenetes-fundamentals

Training projects to explore k8s features
Scala
3
star
92

oauth_tutorial

Phoenix OAuth tutorial
Elixir
3
star
93

terraform-gke-bootstrap

HCL
3
star
94

sttp-native-cli

Scala Native with scala-cli and sttp example
Scala
3
star
95

functional-pancakes

Scala
3
star
96

play-scala-slick-example-part2

Scala
3
star
97

ansible-bigbluebutton

Shell
3
star
98

kleisli-example

Scala
3
star
99

loom-protect

Java
3
star
100

supler-example

Example project for Supler http://supler.io
JavaScript
2
star