All Projects → irontitan → tardis

irontitan / tardis

Licence: GPL-3.0 license
Event sourcing toolkit

Programming Languages

typescript
32286 projects

Projects that are alternatives of or similar to tardis

Goes
Go Event Sourcing made easy
Stars: ✭ 144 (+311.43%)
Mutual labels:  events, event-sourcing, event-driven, eventsourcing
Eventflow.example
DDD+CQRS+Event-sourcing examples using EventFlow following CQRS-ES architecture. It is configured with RabbitMQ, MongoDB(Snapshot store), PostgreSQL(Read store), EventStore(GES). It's targeted to .Net Core 2.2 and include docker compose file.
Stars: ✭ 131 (+274.29%)
Mutual labels:  event-sourcing, event-driven, eventsourcing
Eventhorizon
CQRS/ES toolkit for Go
Stars: ✭ 961 (+2645.71%)
Mutual labels:  event-sourcing, entity, eventsourcing
EventEmitter
Simple EventEmitter with multiple listeners
Stars: ✭ 19 (-45.71%)
Mutual labels:  events, event-sourcing, event-driven
Event Driven Spring Boot
Example Application to demo various flavours of handling domain events in Spring Boot
Stars: ✭ 194 (+454.29%)
Mutual labels:  events, event-sourcing, event-driven
Watermill
Building event-driven applications the easy way in Go.
Stars: ✭ 3,504 (+9911.43%)
Mutual labels:  events, event-sourcing, event-driven
Totem
Knowledge work at play
Stars: ✭ 56 (+60%)
Mutual labels:  event-sourcing, eventsourcing
Kledex
.NET Standard framework to create simple and clean design. Advanced features for DDD, CQRS and Event Sourcing.
Stars: ✭ 502 (+1334.29%)
Mutual labels:  events, event-sourcing
Remit
RabbitMQ-backed microservices supporting RPC, pubsub, automatic service discovery and scaling with no code changes.
Stars: ✭ 24 (-31.43%)
Mutual labels:  events, event-driven
Eventsourcing
Event Sourcing Library for Rust
Stars: ✭ 71 (+102.86%)
Mutual labels:  events, event-sourcing
Event Sourcing Cqrs Examples
Event Sourcing and CQRS in practice.
Stars: ✭ 265 (+657.14%)
Mutual labels:  events, event-sourcing
Diff Table
Stars: ✭ 21 (-40%)
Mutual labels:  events, eventsourcing
Dermayon
Dermayon is Library for supporting build large application,distributed application, scalable, microservices, cqrs, event sourcing, including generic ef repository pattern with unit of work, generic mongo repository pattern with unit of work, kafka, etc
Stars: ✭ 66 (+88.57%)
Mutual labels:  event-sourcing, event-driven
evon
Fast and versatile event dispatcher code generator for Golang
Stars: ✭ 15 (-57.14%)
Mutual labels:  events, event-driven
Message Io
Event-driven message library for building network applications easy and fast.
Stars: ✭ 321 (+817.14%)
Mutual labels:  events, event-driven
Laravel Event Projector
Event sourcing for Artisans 📽
Stars: ✭ 650 (+1757.14%)
Mutual labels:  events, event-sourcing
Attendize
Attendize is an open-source ticket selling and event management platform built on Laravel.
Stars: ✭ 3,285 (+9285.71%)
Mutual labels:  events, event-management
Eventing
Open source specification and implementation of Knative event binding and delivery
Stars: ✭ 980 (+2700%)
Mutual labels:  events, event-driven
Eventflow
Async/await first CQRS+ES and DDD framework for .NET
Stars: ✭ 1,932 (+5420%)
Mutual labels:  events, eventsourcing
Noel
A universal, human-centric, replayable javascript event emitter.
Stars: ✭ 158 (+351.43%)
Mutual labels:  events, event-driven

Event sourcing library to help developers abstract core concepts

Build Status GitHub license Javascript code Style Github All Releases GitHub package version Codacy Badge Known Vulnerabilities

Summary

Instalation

Simply run

NPM:

$ npm install @irontitan/tardis

Yarn:

$ yarn add @irontitan/tardis

Usage

Javascript:

Person.js

const PersonWasCreated = require('./PersonWasCreatedEvent')
const PersonEmailWasAdded = require('./PersonEmailWasAddedEvent')

class Person {
  constructor () {
    this.name = null
    this.email = null
    this.updatedAt = null
    this.updatedBy = null
  }

  static create (name, user, email) {
    return [
      new PersonWasCreated({ name }, user),
      new PersonEmailWasAdded({ email }, user)
    ]
  }

  get state () {
    return { ...this }
  }
}

module.exports = Person

PersonWasCreatedEvent.js

const { Event } = require('@irontitan/tardis')

class PersonWasCreated extends Event {
  static get eventName () { return 'person-was-created' }

  constructor (data, user) {
    super(PersonWasCreated.eventName, data)
    this.user = user
  }

  static commit (state, event) {
    state.name = event.data.name
    state.updatedAt = event.timestamp
    state.updatedBy = event.user
    return state
  }
}

module.exports = PersonWasCreated

PersonEmailWasAddedEvent.js

const { Event } = require('@irontitan/tardis')

class PersonEmailWasAdded extends Event {
  static get eventName () { return 'person-email-was-added' }

  constructor (data, user) {
    super(PersonEmailWasAdded.eventName, data)
    this.user = user
  }

  static commit (state, event) {
    state.email = event.data.email
    state.updatedAt = event.timestamp
    state.updatedBy = event.user
    return state
  }
}

module.exports = PersonEmailWasAdded

index.js

const { Reducer } = require('@irontitan/tardis')
const Person = require('./Person')
const PersonWasCreated = require('./PersonWasCreatedEvent')
const PersonEmailWasAdded = require('./PersonEmailWasAddedEvent')

const personReducer = new Reducer({
  [PersonWasCreated.eventName]: PersonWasCreated.commit,
  [PersonEmailWasAdded.eventName]: PersonEmailWasAdded.commit
})

const events = [ ...Person.create('John Doe', 'jdoe', '[email protected]') ]
console.log(events)
/*
[
  PersonWasCreated {
    id: 'd043fc61a0ab5e088a458b6b',
    name: 'person-was-created',
    data: { name: 'John Doe' },
    timestamp: 2018-10-16T22:10:06.022Z,
    user: 'johndoe'
  },
  PersonEmailWasAdded {
    id: 'a2707db386e25529e25ee882',
    name: 'person-email-was-added',
    data: { email: '[email protected]' },
    timestamp: 2018-10-16T22:10:06.022Z,
    user: 'johndoe'
  }
]
*/

const person = personReducer.reduce(new Person(), events)
console.log(person.state)
/*
{
  name: 'John Doe',
  email: '[email protected]',
  updatedBy: 'johndoe',
  updatedAt: 2018-10-16T20:54:57.122Z
}
*/

Typescript:

Person.ts

import PersonWasCreated from './PersonWasCreatedEvent'
import PersonEmailWasAdded from './PersonEmailWasAddedEvent'

export default class Person {
  name: string | null = null
  email: string | null = null
  updatedAt: Date | null = null
  updatedBy: string | null = null

  static create(name: string, user: string, email: string) {
    return [
      new PersonWasCreated({ name }, user),
      new PersonEmailWasAdded({ email }, user)
    ]
  }

  public get state() {
    return {
      name: this.name,
      email: this.email,
      updatedAt: this.updatedAt,
      updatedBy: this.updatedBy
    }
  }
}

PersonWasCreatedEvent.ts

import { Event } from '@irontitan/tardis'

export interface IPersonCreationParams {
  name: string
}

export default class PersonWasCreated extends Event<IPersonCreationParams> {
  static eventName = 'person-was-created'
  user: string

  constructor(data: IPersonCreationParams, user: string) {
    super(PersonWasCreated.eventName, data)
    this.user = user
  }

  static commit(state: Person, event: PersonWasCreated): Person {
    state.name = event.data.name
    state.updatedAt = event.timestamp
    state.updatedBy = event.user
    return state
  }
}

PersonEmailWasAddedEvent.ts

import { Event } from '@irontitan/tardis'

export default class PersonEmailWasAdded extends Event<any> {
  static eventName = 'person-email-was-added'
  user: string

  constructor(data: { email: string }, user: string) {
    super(PersonEmailWasAdded.eventName, data)
    this.user = user
  }

  static commit(state: Person, event: PersonEmailWasAdded): Person {
    state.email = event.data.email
    state.updatedAt = event.timestamp
    state.updatedBy = event.user
    return state
  }
}

index.ts

import { Reducer } from '@irontitan/tardis'
import Person from './Person'
import PersonWasCreated from './PersonWasCreatedEvent'
import PersonEmailWasAdded from './PersonEmailWasAddedEvent'

const personReducer = new Reducer<Person>({
  [PersonWasCreated.eventName]: PersonWasCreated.commit,
  [PersonEmailWasAdded.eventName]: PersonEmailWasAdded.commit
})

const events = [ ...Person.create('John Doe', 'jdoe', '[email protected]') ]
console.log(events)
/*
[
  PersonWasCreated {
    id: 'd043fc61a0ab5e088a458b6b',
    name: 'person-was-created',
    data: { name: 'John Doe' },
    timestamp: 2018-10-16T22:10:06.022Z,
    user: 'johndoe'
  },
  PersonEmailWasAdded {
    id: 'a2707db386e25529e25ee882',
    name: 'person-email-was-added',
    data: { email: '[email protected]' },
    timestamp: 2018-10-16T22:10:06.022Z,
    user: 'johndoe'
  }
]
*/

const person = personReducer.reduce(new Person(), events)
console.log(person.state)
/*
{
  name: 'John Doe',
  email: '[email protected]',
  updatedBy: 'johndoe',
  updatedAt: 2018-10-16T20:54:57.122Z
}
*/

Core concepts

The core idea behind Tardis is to implement a generic and easy-to-use interface to interact with the Event Sourcing architecture.

In this architecture there are two core concepts: The Event and the Reducer, and we'll call anything from our business logic Entity, it can be a car, a person, a ship or anything else.

Event

An event represents something that happened to your entity. For instance "The Person was created at the database", or even "The person has visited Amsterdam" and so on. Every event receives two arguments: The event name string (lowercase and hyphenated, so Person was created becomes person-was-created), and all the data for that event. It also contains a timestamp and an id property which will be automatically generated.

All events must also include a static name property and a static commit method. The commit method is a function with the following signature:

static commit (state, event) {

}

The commit method is where the state will be updated and sent back to the reducer.

Let's say we have this entity:

const { PersonWasCreated } = require('../your/path/to/events')

class Person {
  constructor () {
    this.name = null
    this.updatedBy = null
    this.updatedAt = null
  }

  // This method will return every event needed to create the user
  static create (name, user) {
    return [
      new PersonWasCreated({ name }, user)
    ]
  }

  get state () {
    return { ...this }
  }
}

So, let's construct an Event:

Typescript:

import { Event } from '@irontitan/tardis'

class PersonWasCreated extends Event { // Our event's class
  static eventName = 'person-was-created' // Event name
  public user: string // A custom user property belonging to the event itself

  // The event constructor takes as many arguments as your event needs
  // In our case, we only have two: The data which is related to the entity "Person" and the user who generated the event
  constructor (data: { name: string }, user: string) {
    super(PersonWasCreated.eventName, data) // Calling the parent class with the name and entity data
    this.user = user
  }

  // Commit method which will update the current received state
  // State is an instance of your own entity with all the properties you need
  // Our entity has only the property "name"
  static commit (state: Person, event: PersonWasCreated) {
    // Updating the state
    state.name = event.data.name
    state.updatedAt = event.timestamp
    state.updatedBy = event.user
    return state
  }
}

Javascript

const { Event } = require('@irontitan/tardis')

class PersonWasCreated extends Event {
  static get eventName () { return 'person-was-created' }

  constructor (data, user) {
    super(PersonWasCreated.eventName, data)
    this.user = user
  }

  static commit (state, event) {
    state.name = event.data.name
    state.updatedAt = event.timestamp
    state.updatedBy = event.user
    return state
  }
}

Events with custom ID's

By default, the Event class will generate a MongoDB-Like ObjectID. If you wish to use your own ID function, then it is needed to supply it to the third optional parameter in the super method inside your event:

Typescript

import { Event } from '@irontitan/tardis'
import crypto from 'crypto'

function createId (): string {
  return crypto.randomBytes(12).toString('hex')
}

class PersonWasCreated extends Event {
  static eventName = 'person-was-created'
  public user: string

  constructor (data: { name: string }, user: string) {
    super(PersonWasCreated.eventName, data, createId())
    this.user = user
  }

  static commit (state: Person, event: PersonWasCreated) {
    state.name = event.data.name
    state.updatedAt = event.timestamp
    state.updatedBy = event.user
    return state
  }
}

Javascript

const crypto = require('crypto')
const { Event } = require('@irontitan/tardis')

function createId () {
  return crypto.randomBytes(12).toString('hex')
}

class PersonWasCreated extends Event {
  static get eventName () { return 'person-was-created' }

  constructor (data, user) {
    super(PersonWasCreated.eventName, data, createId())
    this.user = user
  }

  static commit (state, event) {
    state.name = event.data.name
    state.updatedAt = event.timestamp
    state.updatedBy = event.user
    return state
  }
}

Reducer

The reducer is a structure that represents the applied state. It will be the reducer which will receive an initial state and recursively apply all the commit functions present in events, passing the resulting state to the initial state of the next call, essentially, reducing an array of events to a single final event.

All reducers must be instantiated by passing an object of known events and it's respective commit functions, for instance, let's get our Person's events:

Typescript

import { Person } from './your/entity/path'
import { Reducer } from '@irontitan/tardis'
import { PersonWasCreated, PersonWasUpdated } from './your/path/to/events'

const personReducer = new Reducer<Person>({
  [PersonWasCreated.eventName]: PersonWasCreated.commit,
  [PersonWasUpdated.eventName]: PersonWasUpdated.commit
})

Javascript

const { Person } = require('./your/entity/path')
const { Reducer } = require('@irontitan/tardis')
const { PersonWasCreated, PersonWasUpdated } = require('./your/path/to/events')

const personReducer = new Reducer({
  [PersonWasCreated.eventName]: PersonWasCreated.commit,
  [PersonEmailWasAdded.eventName]: PersonEmailWasAdded.commit
})

Now we have an instance of the reducer which will only apply commits to that specific known events. Now we can create an event and pass to it so it will update our entity:

Typescript

import { Person } from './your/entity/path'
import { Reducer } from '@irontitan/tardis'
import { PersonWasCreated, PersonWasUpdated } from './your/path/to/events'

const events = [...Person.create('John Doe', 'jdoe')]

console.log(events)
/*
[
  PersonWasCreated {
    id: 'd043fc61a0ab5e088a458b6b',
    name: 'person-was-created',
    data: { name: 'John Doe' },
    timestamp: 2018-10-16T22:10:06.022Z,
    user: 'jdoe'
  }
]
*/

let initialState = new Person()
console.log(initialState.state)
/*
{
  name: null
  updatedBy: null
  updatedAt: null
}
*/

const person = personReducer.reduce(initialState, events)
console.log(person.state)
/*
{
  name: 'John Doe',
  updatedBy: 'jdoe',
  updatedAt: 2018-10-16T20:54:57.122Z
}
*/

Javascript

const { Person } = require('./your/entity/path')
const { Reducer } = require('@irontitan/tardis')
const { PersonWasCreated, PersonWasUpdated } = require('./your/path/to/events')

const events = [...Person.create('John Doe', 'jdoe')]

console.log(events)
/*
[
  PersonWasCreated {
    id: 'd043fc61a0ab5e088a458b6b',
    name: 'person-was-created',
    data: { name: 'John Doe' },
    timestamp: 2018-10-16T22:10:06.022Z,
    user: 'jdoe'
  }
]
*/

let initialState = new Person()
console.log(initialState.state)
/*
{
  name: null
  updatedBy: null
  updatedAt: null
}
*/

const person = personReducer.reduce(initialState, events)
console.log(person.state)
/*
{
  name: 'John Doe',
  updatedBy: 'jdoe',
  updatedAt: 2018-10-16T20:54:57.122Z
}
*/
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].