Last active
May 24, 2018 14:41
-
-
Save mgenov/98b72f3a6b08528a6991eaf36acdf553 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
const createEventStore = () => { | |
let data = {} | |
let subscribers = [] | |
let store = { | |
saveEvents: (aggregateId, events) => { | |
if (!data[aggregateId]) { | |
data[aggregateId] = [] | |
} | |
events.forEach(event => { | |
data[aggregateId].push(event) | |
}) | |
subscribers.forEach(sub => { | |
events.forEach(event => { | |
sub(event) | |
}) | |
}) | |
}, | |
getEvents: async aggregateId => { | |
//TODO: load data from firestore | |
return Promise.resolve(data[aggregateId]) | |
}, | |
subscribe: subscriber => { | |
subscribers.push(subscriber) | |
} | |
} | |
return store | |
} | |
class AggregateRootBase { | |
constructor() { | |
this.uncommittedEvents = [] | |
} | |
applyChange = (event) => { | |
this.uncommittedEvents.push(event) | |
} | |
getUncommittedEvents = () => { | |
return this.uncommittedEvents | |
} | |
markEventsAsCommitted = () => { | |
this.uncommittedEvents = [] | |
} | |
loadFromEvents = (events) => { | |
const names = Object.getOwnPropertyNames(this) | |
let applyFunc =names.filter(name => name === 'apply') | |
if (applyFunc.size === 0) { | |
return | |
} | |
let applyCall = this[applyFunc[0]] | |
events.forEach(event => { | |
applyCall(event) | |
}) | |
} | |
} | |
class AggregateRepository { | |
constructor(eventStore) { | |
this.eventStore = eventStore | |
} | |
getById = async (id, aggregateType) => { | |
let t = new aggregateType() | |
let events = await this.eventStore.getEvents(id) | |
t.loadFromEvents(events) | |
return t | |
} | |
save = (id, aggregate) => { | |
let events = aggregate.getUncommittedEvents() | |
this.eventStore.saveEvents(id, events) | |
aggregate.markEventsAsCommitted() | |
} | |
} | |
class Customer extends AggregateRootBase { | |
constructor(id, name, age) { | |
super() | |
this.name = '' | |
this.age = 0 | |
this.applyChange({type: 'CustomerCreatedEvent', id: id, payload: {name: name, age: age}}) | |
} | |
changeName = (newName) => { | |
this.applyChange({type: 'CustomerNameUpdatedEvent', id: this.id, payload: {name: newName}}) | |
} | |
apply = (event) => { | |
if (event.type === 'CustomerCreatedEvent') { | |
this.name = event.payload.name | |
this.age = event.payload.age | |
} | |
if (event.type === 'CustomerNameUpdatedEvent') { | |
this.name = event.payload.name | |
} | |
} | |
} | |
let eventStore = createEventStore() | |
let aggregateRepository = new AggregateRepository(eventStore) | |
const putAndGet = async () => { | |
let savingCustomer = new Customer(1, 'Peter', 30) | |
aggregateRepository.save(1, savingCustomer) | |
savingCustomer.changeName('John') | |
aggregateRepository.save(1, savingCustomer) | |
let c2 = await aggregateRepository.getById(1, Customer) | |
console.log(c2.name + ', ' + c2.age) | |
} | |
putAndGet() |
This file contains hidden or 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
import configureStore from 'redux-mock-store' //ES6 modules | |
const createEventStore = () => { | |
let data = {} | |
let subscribers = [] | |
let store = { | |
saveEvents: (aggregateId, events) => { | |
data[aggregateId] = events | |
subscribers.forEach(sub => { | |
events.forEach(event => { | |
sub(event) | |
}) | |
}) | |
}, | |
getEvents: async aggregateId => { | |
//TODO: load data from firestore | |
return Promise.resolve(data[aggregateId]) | |
}, | |
subscribe: subscriber => { | |
subscribers.push(subscriber) | |
} | |
} | |
return store | |
} | |
const createOfflineMiddleware = (eventStore, handlers) => { | |
return store => next => action => { | |
if (handlers[action.type]) { | |
let event = handlers[action.type](action) | |
eventStore.saveEvents(action.aggregateId, [event]) | |
} | |
let result = next(action) | |
return result | |
} | |
} | |
const eventStore = createEventStore() | |
const offlineMiddleware = createOfflineMiddleware(eventStore, { | |
CreateOrder: action => { | |
return { | |
type: 'OrderPlacedEvent', | |
payload: { customer: action.payload.customer } | |
} | |
} | |
}) | |
const mockStore = configureStore([offlineMiddleware]) | |
describe('Middleware', () => { | |
it('should handle received event', async () => { | |
const initialState = {} | |
const store = mockStore(initialState) | |
store.dispatch({ | |
type: 'CreateOrder', | |
aggregateId: 123, | |
payload: { customer: 'John', lines: [] } | |
}) | |
let events = await eventStore.getEvents(123) | |
expect(events).toEqual([ | |
{ type: 'OrderPlacedEvent', payload: { customer: 'John' } } | |
]) | |
}) | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment