Last active
February 25, 2019 03:59
-
-
Save haruair/7da296b853e0866a9d2fdeb9372bc99c to your computer and use it in GitHub Desktop.
Event Sourcing Pattern in JS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
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