eventric
Version:
behavior-first application development
386 lines (279 loc) • 11.4 kB
text/coffeescript
# TODO: Split up Context into smaller modules
class Context
constructor: (@name, @_eventric) ->
@_initialized = false
@_params = @_eventric.get()
@_di = {}
@_aggregateRootClasses = {}
@_commandHandlers = {}
@_queryHandlers = {}
@_domainEventClasses = {}
@_domainEventHandlers = {}
@_projectionClasses = {}
@_domainEventStreamClasses = {}
@_domainEventStreamInstances = {}
@_repositoryInstances = {}
@_storeClasses = {}
@_storeInstances = {}
@_eventBus = new @_eventric.EventBus @_eventric
@projectionService = new @_eventric.Projection @_eventric
@log = @_eventric.log
set: (key, value) ->
@_params[key] = value
@
get: (key) ->
@_params[key]
emitDomainEvent: (domainEventName, domainEventPayload) =>
DomainEventClass = @getDomainEvent domainEventName
if !DomainEventClass
throw new Error "Tried to emitDomainEvent '#{domainEventName}' which is not defined"
domainEvent = @_createDomainEvent domainEventName, DomainEventClass, domainEventPayload
@saveAndPublishDomainEvent domainEvent
.then =>
@_eventric.log.debug "Created and Handled DomainEvent in Context", domainEvent
publishDomainEvent: (domainEvent) =>
@_eventBus.publishDomainEvent domainEvent
_createDomainEvent: (domainEventName, DomainEventClass, domainEventPayload) ->
new @_eventric.DomainEvent
id: @_eventric.generateUid()
name: domainEventName
context: @name
payload: new DomainEventClass domainEventPayload
# TODO: Consider renaming. What store? event store? read model store?
addStore: (storeName, StoreClass, storeOptions={}) ->
@_storeClasses[storeName] =
Class: StoreClass
options: storeOptions
@
defineDomainEvent: (domainEventName, DomainEventClass) ->
@_domainEventClasses[domainEventName] = DomainEventClass
@
defineDomainEvents: (domainEventClassesObj) ->
@defineDomainEvent domainEventName, DomainEventClass for domainEventName, DomainEventClass of domainEventClassesObj
@
addCommandHandler: (commandHandlerName, commandHandlerFn) ->
@_commandHandlers[commandHandlerName] = commandHandlerFn
@
_getAggregateRepository: (aggregateName, command) =>
repositoriesCache = {} if not repositoriesCache
if not repositoriesCache[aggregateName]
AggregateRoot = @_aggregateRootClasses[aggregateName]
repository = new @_eventric.Repository
aggregateName: aggregateName
AggregateRoot: AggregateRoot
context: @
eventric: @_eventric
repositoriesCache[aggregateName] = repository
repositoriesCache[aggregateName].setCommand command
repositoriesCache[aggregateName]
addCommandHandlers: (commandObj) ->
@addCommandHandler commandHandlerName, commandFunction for commandHandlerName, commandFunction of commandObj
@
addQueryHandler: (queryHandlerName, queryHandlerFn) ->
@_queryHandlers[queryHandlerName] = queryHandlerFn
@
addQueryHandlers: (queryObj) ->
@addQueryHandler queryHandlerName, queryFunction for queryHandlerName, queryFunction of queryObj
@
addAggregate: (aggregateName, AggregateRootClass) ->
@_aggregateRootClasses[aggregateName] = AggregateRootClass
@
addAggregates: (aggregatesObj) ->
@addAggregate aggregateName, AggregateRootClass for aggregateName, AggregateRootClass of aggregatesObj
@
subscribeToDomainEvent: (domainEventName, handlerFn) ->
domainEventHandler = () => handlerFn.apply @_di, arguments
@_eventBus.subscribeToDomainEvent domainEventName, domainEventHandler
subscribeToDomainEvents: (domainEventHandlersObj) ->
@subscribeToDomainEvent domainEventName, handlerFn for domainEventName, handlerFn of domainEventHandlersObj
# TODO: Remove this when stream subscriptions are implemented
subscribeToDomainEventWithAggregateId: (domainEventName, aggregateId, handlerFn) ->
domainEventHandler = () => handlerFn.apply @_di, arguments
@_eventBus.subscribeToDomainEventWithAggregateId domainEventName, aggregateId, domainEventHandler
subscribeToAllDomainEvents: (handlerFn) ->
domainEventHandler = () => handlerFn.apply @_di, arguments
@_eventBus.subscribeToAllDomainEvents domainEventHandler
addProjection: (projectionName, ProjectionClass) ->
@_projectionClasses[projectionName] = ProjectionClass
@
addProjections: (viewsObj) ->
@addProjection projectionName, ProjectionClass for projectionName, ProjectionClass of viewsObj
@
getProjectionInstance: (projectionId) ->
@projectionService.getInstance projectionId
destroyProjectionInstance: (projectionId) ->
@projectionService.destroyInstance projectionId, @
initializeProjectionInstance: (projectionName, params) ->
if not @_projectionClasses[projectionName]
err = "Given projection #{projectionName} not registered on context"
@_eventric.log.error err
err = new Error err
return err
@projectionService.initializeInstance projectionName, @_projectionClasses[projectionName], params, @
initialize: ->
new Promise (resolve, reject) =>
@log.debug "[#{@name}] Initializing"
@log.debug "[#{@name}] Initializing Store"
@_initializeStores()
.then =>
@log.debug "[#{@name}] Finished initializing Store"
@_di =
$query: => @query.apply @, arguments
$projectionStore: => @getProjectionStore.apply @, arguments
$emitDomainEvent: => @emitDomainEvent.apply @, arguments
.then =>
@log.debug "[#{@name}] Initializing Projections"
@_initializeProjections()
.then =>
@log.debug "[#{@name}] Finished initializing Projections"
@log.debug "[#{@name}] Finished initializing"
@_initialized = true
resolve()
.catch (err) ->
reject err
_initializeStores: ->
new Promise (resolve, reject) =>
stores = []
for storeName, store of (@_eventric.defaults @_storeClasses, @_eventric.getStores())
stores.push
name: storeName
Class: store.Class
options: store.options
@_eventric.eachSeries stores, (store, next) =>
@log.debug "[#{@name}] Initializing Store #{store.name}"
@_storeInstances[store.name] = new store.Class
@_storeInstances[store.name].initialize @, store.options
.then =>
@log.debug "[#{@name}] Finished initializing Store #{store.name}"
next()
.catch (err) ->
next err
, (err) ->
return reject err if err
resolve()
_initializeProjections: ->
new Promise (resolve, reject) =>
projections = []
for projectionName, ProjectionClass of @_projectionClasses
projections.push
name: projectionName
class: ProjectionClass
@_eventric.eachSeries projections, (projection, next) =>
eventNames = null
@log.debug "[#{@name}] Initializing Projection #{projection.name}"
@projectionService.initializeInstance projection.name, projection.class, {}, @
.then (projectionId) =>
@log.debug "[#{@name}] Finished initializing Projection #{projection.name}"
next()
.catch (err) ->
reject err
, (err) =>
return reject err if err
resolve()
getProjection: (projectionId) ->
@projectionService.getInstance projectionId
# TODO: Rename to getDomainEventClass
getDomainEvent: (domainEventName) ->
@_domainEventClasses[domainEventName]
getDomainEventsStore: ->
storeName = @get 'default domain events store'
@_storeInstances[storeName]
saveAndPublishDomainEvent: (domainEvent) -> new Promise (resolve, reject) =>
@getDomainEventsStore().saveDomainEvent domainEvent
.then =>
@publishDomainEvent domainEvent
.then (err) ->
return reject err if err
resolve domainEvent
# TODO: Remove this when stream subscriptions are implemented
findDomainEventsByName: (findArguments...) ->
new Promise (resolve, reject) =>
@getDomainEventsStore().findDomainEventsByName findArguments..., (err, events) ->
return reject err if err
resolve events
# TODO: Remove this when stream subscriptions are implemented
findDomainEventsByNameAndAggregateId: (findArguments...) ->
new Promise (resolve, reject) =>
@getDomainEventsStore().findDomainEventsByNameAndAggregateId findArguments..., (err, events) ->
return reject err if err
resolve events
getProjectionStore: (storeName, projectionName) => new Promise (resolve, reject) =>
if not @_storeInstances[storeName]
err = "Requested Store with name #{storeName} not found"
@log.error err
return reject err
@_storeInstances[storeName].getProjectionStore projectionName
.then (projectionStore) ->
resolve projectionStore
.catch (err) ->
reject err
clearProjectionStore: (storeName, projectionName) => new Promise (resolve, reject) =>
if not @_storeInstances[storeName]
err = "Requested Store with name #{storeName} not found"
@log.error err
return reject err
@_storeInstances[storeName].clearProjectionStore projectionName
.then ->
resolve()
.catch (err) ->
reject err
getEventBus: ->
@_eventBus
command: (name, params) ->
new Promise (resolve, reject) =>
command =
id: @_eventric.generateUid()
name: name
params: params
@log.debug 'Got Command', command
if not @_initialized
err = 'Context not initialized yet'
@log.error err
err = new Error err
return reject err
if not @_commandHandlers[name]
err = "Given command #{name} not registered on context"
@log.error err
err = new Error err
return reject err
_di = {}
for diFnName, diFn of @_di
_di[diFnName] = diFn
_di.$aggregate =
create: (aggregateName, aggregateParams...) =>
repository = @_getAggregateRepository aggregateName, command
repository.create aggregateParams...
load: (aggregateName, aggregateId) =>
repository = @_getAggregateRepository aggregateName, command
repository.findById aggregateId
executeCommand = null
commandHandlerFn = @_commandHandlers[name]
executeCommand = commandHandlerFn.apply _di, [params]
Promise.all [executeCommand]
.then ([result]) =>
@log.debug 'Completed Command', name
resolve result
.catch (error) ->
reject error
query: (name, params) ->
new Promise (resolve, reject) =>
@log.debug 'Got Query', name
if not @_initialized
err = 'Context not initialized yet'
@log.error err
err = new Error err
reject err
return
if not @_queryHandlers[name]
err = "Given query #{name} not registered on context"
@log.error err
err = new Error err
return reject err
executeQuery = @_queryHandlers[name].apply @_di, [params]
Promise.all [executeQuery]
.then ([result]) =>
@log.debug "Completed Query #{name} with Result #{result}"
resolve result
.catch (err) ->
reject err
module.exports = Context