Skip to content

Instantly share code, notes, and snippets.

@haruair
Last active February 25, 2019 03:59
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save haruair/7da296b853e0866a9d2fdeb9372bc99c to your computer and use it in GitHub Desktop.
Save haruair/7da296b853e0866a9d2fdeb9372bc99c to your computer and use it in GitHub Desktop.
Event Sourcing Pattern in JS
class AggregateRoot {
apply(event) {
this.handle(event)
return this
}
handle(event) {
var eventName = event.constructor.name
var eventMethod = `apply${eventName}`
if (! this[eventMethod]) {
throw new TypeError(`${eventMethod} is not a function`)
}
this[eventMethod](event)
}
}
class OpenedEvent {
constructor(id: number, name: string) {
this.id = id
this.name = name
}
}
class BankAccount extends AggregateRoot {
static open(id: number, name: string) {
var bankAccount = new BankAccount
bankAccount.apply(new OpenedEvent(id, name))
return bankAccount
}
applyOpenedEvent(event) {
this.id = event.id
this.name = event.name
this.closed = false
this.balance = 0
}
}
var bankAccount = BankAccount.open(123456, 'Koala')
console.log(bankAccount.id, bankAccount.name) // 123456, 'Koala'
class AggregateRoot {
apply(event) {
this.handle(event)
return this
}
handle(event) {
var eventName = event.constructor.name
var eventMethod = `apply${eventName}`
if (! this[eventMethod]) {
throw new TypeError(`${eventMethod} is not a function`)
}
this[eventMethod](event)
}
}
class OpenedEvent {
constructor(id: number, name: string) {
this.id = id
this.name = name
}
}
class WithdrawnEvent {
constructor(id: number, amount: number) {
this.id = id
this.amount = amount
}
}
class DepositedEvent {
constructor(id: number, amount: number) {
this.id = id
this.amount = amount
}
}
class ClosedEvent {
constructor(id: number) {
this.id = id
}
}
class BankAccount extends AggregateRoot {
static open(id: number, name: string) {
var bankAccount = new BankAccount
bankAccount.apply(new OpenedEvent(id, name))
return bankAccount
}
withdraw(amount) {
if (this.closed) {
throw new Error(`${this.id} account is closed.`)
}
this.apply(new WithdrawnEvent(this.id, amount))
return this
}
deposit(amount) {
if (this.closed) {
throw new Error(`${this.id} account is closed.`)
}
this.apply(new DepositedEvent(this.id, amount))
return this
}
close() {
if (!this.closed) {
this.apply(new ClosedEvent(this.id))
}
return this
}
applyOpenedEvent(event) {
this.id = event.id
this.name = event.name
this.closed = false
this.balance = 0
}
applyWithdrawnEvent(event) {
this.balance -= event.amount
}
applyDepositedEvent(event) {
this.balance += event.amount
}
applyClosedEvent(event) {
this.closed = true
}
}
var bankAccount = BankAccount.open(123456, 'Koala')
.deposit(10000)
.withdraw(1000)
.deposit(3000)
.close()
console.log(bankAccount.name, bankAccount.balance, bankAccount.closed ? 'closed' : 'opened')
// Koala 12000 closed
var events = [
new OpenedEvent(123456, 'Koala'),
new DepositedEvent(123456, 10000),
new WithdrawnEvent(123456, 1000),
new DepositedEvent(123456, 3000),
new ClosedEvent(123456),
]
var bankAccount2 = new BankAccount
events.forEach(event => bankAccount2.apply(event))
console.log(bankAccount2.name, bankAccount2.balance, bankAccount2.closed ? 'closed' : 'opened')
// Koala 12000 closed
class AggregateRoot {
uncommittedEvents = []
apply(event) {
this.handle(event)
this.uncommittedEvents.push(event)
return this
}
handle(event) {
var eventName = event.constructor.name
var eventMethod = `apply${eventName}`
if (! this[eventMethod]) {
throw new TypeError(`${eventMethod} is not a function`)
}
this[eventMethod](event)
}
initializeState(events) {
events.forEach(event => this.handle(event))
}
getUncommittedEvents() {
var events = this.uncommittedEvents
this.uncommittedEvents = []
return events
}
}
class OpenedEvent {
constructor(id: number, name: string) {
this.id = id
this.name = name
}
}
class WithdrawnEvent {
constructor(id: number, amount: number) {
this.id = id
this.amount = amount
}
}
class DepositedEvent {
constructor(id: number, amount: number) {
this.id = id
this.amount = amount
}
}
class ClosedEvent {
constructor(id: number) {
this.id = id
}
}
class BankAccount extends AggregateRoot {
static open(id: number, name: string) {
var bankAccount = new BankAccount
bankAccount.apply(new OpenedEvent(id, name))
return bankAccount
}
withdraw(amount) {
if (this.closed) {
throw new Error(`${this.id} account is closed.`)
}
this.apply(new WithdrawnEvent(this.id, amount))
return this
}
deposit(amount) {
if (this.closed) {
throw new Error(`${this.id} account is closed.`)
}
this.apply(new DepositedEvent(this.id, amount))
return this
}
close() {
if (!this.closed) {
this.apply(new ClosedEvent(this.id))
}
return this
}
applyOpenedEvent(event) {
this.id = event.id
this.name = event.name
this.closed = false
this.balance = 0
}
applyWithdrawnEvent(event) {
this.balance -= event.amount
}
applyDepositedEvent(event) {
this.balance += event.amount
}
applyClosedEvent(event) {
this.closed = true
}
}
class EventSourcingRepository {
constructor(eventStore, aggregateType) {
this.eventStore = eventStore
this.aggregateType = aggregateType
}
load(id) {
var events = this.eventStore.load(id)
var aggregate = Object.create(this.aggregateType.prototype)
aggregate.initializeState(events)
return aggregate
}
save(aggregate) {
var uncommittedEvents = aggregate.getUncommittedEvents()
this.eventStore.append(uncommittedEvents)
}
}
class EventStoreData {
constructor(rootId, event, createdAt) {
this.rootId = rootId
this.event = event
this.createdAt = createdAt
}
}
class InMemoryForTestingEventStore {
constructor(events) {
this.data = events ? this.convertEvents(events) : []
}
load(rootId) {
return this.data
.filter(data => data.rootId === rootId)
.map(data => data.event)
}
append(events) {
var newData = this.convertEvents(events)
this.data = this.data.concat(newData)
}
convertEvents(events) {
return events.map(event => this.convertEventToData(event))
}
convertEventToData(event) {
var createdAt = new Date().getTime()
return new EventStoreData(event.id, event, createdAt)
}
}
var eventStore = new InMemoryForTestingEventStore()
var repository = new EventSourcingRepository(eventStore, BankAccount)
var bankAccount = BankAccount.open(654321, 'Edward')
.deposit(20000)
.withdraw(1000)
.withdraw(1000)
repository.save(bankAccount)
var loaded = repository.load(654321)
console.log(bankAccount.name, bankAccount.balance, bankAccount.closed ? 'closed' : 'opened')
// Edward 18000 opened
console.log(eventStore.data)
@haruair
Copy link
Author

haruair commented Oct 18, 2017

detail of eventStore.data

@haruair
Copy link
Author

haruair commented Oct 18, 2017

This code is an example of my post about event-sourcing pattern example in js (kor).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment