eventric
Version:
behavior-first application development
217 lines (168 loc) • 7.71 kB
text/coffeescript
class Projection
constructor: (@_eventric) ->
@log = @_eventric.log
@_handlerFunctions = {}
@_projectionInstances = {}
@_domainEventsApplied = {}
initializeInstance: (projectionName, Projection, params, @_context) ->
new Promise (resolve, reject) =>
if typeof Projection is 'function'
projection = new Projection
else
projection = Projection
if @_context._di
for diName, diFn of @_context._di
projection[diName] = diFn
projectionId = @_eventric.generateUid()
aggregateId = null
projection.$subscribeHandlersWithAggregateId = (_aggregateId) ->
aggregateId = _aggregateId
@log.debug "[#{@_context.name}] Clearing ProjectionStores #{projection.stores} of #{projectionName}"
@_clearProjectionStores projection.stores, projectionName
.then =>
@log.debug "[#{@_context.name}] Finished clearing ProjectionStores of #{projectionName}"
@_injectStoresIntoProjection projectionName, projection
.then =>
@_callInitializeOnProjection projectionName, projection, params
.then =>
@log.debug "[#{@_context.name}] Replaying DomainEvents against Projection #{projectionName}"
@_parseEventNamesFromProjection projection
.then (eventNames) =>
@_applyDomainEventsFromStoreToProjection projectionId, projection, eventNames, aggregateId
.then (eventNames) =>
@log.debug "[#{@_context.name}] Finished Replaying DomainEvents against Projection #{projectionName}"
@_subscribeProjectionToDomainEvents projectionId, projectionName, projection, eventNames, aggregateId
.then =>
@_projectionInstances[projectionId] = projection
event =
id: projectionId
projection: projection
@_context.publish "projection:#{projectionName}:initialized", event
@_context.publish "projection:#{projectionId}:initialized", event
resolve projectionId
.catch (err) ->
reject err
_callInitializeOnProjection: (projectionName, projection, params) ->
new Promise (resolve, reject) =>
if not projection.initialize
@log.debug "[#{@_context.name}] No initialize function on Projection #{projectionName} given, skipping"
return resolve projection
@log.debug "[#{@_context.name}] Calling initialize on Projection #{projectionName}"
projection.initialize params, =>
@log.debug "[#{@_context.name}] Finished initialize call on Projection #{projectionName}"
resolve projection
_injectStoresIntoProjection: (projectionName, projection) ->
new Promise (resolve, reject) =>
if not projection.stores
return resolve()
projection["$store"] ?= {}
@_eventric.eachSeries projection.stores, (projectionStoreName, next) =>
@log.debug "[#{@_context.name}] Injecting ProjectionStore #{projectionStoreName} into Projection #{projectionName}"
@_context.getProjectionStore projectionStoreName, projectionName
.then (projectionStore) =>
if projectionStore
projection["$store"][projectionStoreName] = projectionStore
@log.debug "[#{@_context.name}] Finished Injecting ProjectionStore #{projectionStoreName} into Projection #{projectionName}"
next()
.catch (err) ->
next err
, (err) ->
return reject err if err
resolve()
_clearProjectionStores: (projectionStores, projectionName) ->
new Promise (resolve, reject) =>
if not projectionStores
return resolve()
@_eventric.eachSeries projectionStores, (projectionStoreName, next) =>
@log.debug "[#{@_context.name}] Clearing ProjectionStore #{projectionStoreName} for #{projectionName}"
@_context.clearProjectionStore projectionStoreName, projectionName
.then =>
@log.debug "[#{@_context.name}] Finished clearing ProjectionStore #{projectionStoreName} for #{projectionName}"
next()
.catch (err) ->
next err
, (err) ->
resolve()
_parseEventNamesFromProjection: (projection) ->
new Promise (resolve, reject) =>
eventNames = []
for key, value of projection
if (key.indexOf 'handle') is 0 and (typeof value is 'function')
eventName = key.replace /^handle/, ''
eventNames.push eventName
resolve eventNames
_applyDomainEventsFromStoreToProjection: (projectionId, projection, eventNames, aggregateId) ->
new Promise (resolve, reject) =>
@_domainEventsApplied[projectionId] = {}
if aggregateId
findEvents = @_context.findDomainEventsByNameAndAggregateId eventNames, aggregateId
else
findEvents = @_context.findDomainEventsByName eventNames
findEvents
.then (domainEvents) =>
if not domainEvents or domainEvents.length is 0
return resolve eventNames
@_eventric.eachSeries domainEvents, (domainEvent, next) =>
@_applyDomainEventToProjection domainEvent, projection
.then =>
@_domainEventsApplied[projectionId][domainEvent.id] = true
next()
, (err) ->
return reject err if err
resolve eventNames
.catch (err) ->
reject err
_subscribeProjectionToDomainEvents: (projectionId, projectionName, projection, eventNames, aggregateId) ->
new Promise (resolve, reject) =>
domainEventHandler = (domainEvent, done = ->) =>
if @_domainEventsApplied[projectionId][domainEvent.id]
return done()
@_applyDomainEventToProjection domainEvent, projection
.then =>
@_domainEventsApplied[projectionId][domainEvent.id] = true
event =
id: projectionId
projection: projection
domainEvent: domainEvent
@_context.publish "projection:#{projectionName}:changed", event
@_context.publish "projection:#{projectionId}:changed", event
done()
.catch (err) ->
done err
@_eventric.eachSeries eventNames, (eventName, done) =>
if aggregateId
subscriberPromise = @_context.subscribeToDomainEventWithAggregateId eventName, aggregateId, domainEventHandler
else
subscriberPromise = @_context.subscribeToDomainEvent eventName, domainEventHandler
subscriberPromise
.then (subscriberId) =>
@_handlerFunctions[projectionId] ?= []
@_handlerFunctions[projectionId].push subscriberId
done()
.catch (err) ->
done err
, (err) ->
return reject err if err
resolve()
_applyDomainEventToProjection: (domainEvent, projection) =>
new Promise (resolve, reject) =>
if !projection["handle#{domainEvent.name}"]
@log.debug "Tried to apply DomainEvent '#{domainEvent.name}' to Projection without a matching handle method"
resolve()
return
handleDomainEvent = projection["handle#{domainEvent.name}"] domainEvent
Promise.all [handleDomainEvent]
.then ([result]) ->
resolve result
getInstance: (projectionId) ->
@_projectionInstances[projectionId]
destroyInstance: (projectionId) ->
if not @_handlerFunctions[projectionId]
return @_eventric.log.error 'Missing attribute projectionId'
unsubscribePromises = []
for subscriberId in @_handlerFunctions[projectionId]
unsubscribePromises.push @_context.unsubscribeFromDomainEvent subscriberId
delete @_handlerFunctions[projectionId]
delete @_projectionInstances[projectionId]
Promise.all unsubscribePromises
module.exports = Projection