UNPKG

eventric

Version:

behavior-first application development

386 lines (279 loc) 11.4 kB
# 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