eventric
Version:
Build JavaScript applications with Behaviour-driven Domain Design. Based on DDD, BDD, CQRS and EventSourcing.
761 lines (631 loc) • 20.8 kB
text/coffeescript
eventric = require 'eventric'
Repository = require 'eventric/src/context/repository'
DomainEvent = require 'eventric/src/context/domain_event'
EventBus = require 'eventric/src/event_bus'
PubSub = require 'eventric/src/pub_sub'
projectionService = require 'eventric/src/projection'
class Context extends PubSub
constructor: () ->
super
= false
= eventric.get()
= {}
= {}
= {}
= {}
= {}
= {}
= {}
= {}
= []
= {}
= {}
= {}
= {}
= new EventBus
log: eventric.log
###*
* set
*
* Context
*
*
* > Use as: set(key, value)
* Configure settings for the `context`.
*
*
exampleContext.set 'store', StoreAdapter
*
* {Object} key
* Available keys are: `store` Eventric Store Adapter
###
set: (key, value) ->
[key] = value
@
get: (key) ->
[key]
###*
* emitDomainEvent
*
* Context
*
* emit Domain Event in the context
*
* {String} domainEventName Name of the DomainEvent
* {Object} domainEventPayload payload for the DomainEvent
###
emitDomainEvent: (domainEventName, domainEventPayload) =>
DomainEventClass = domainEventName
if !DomainEventClass
throw new Error "Tried to emitDomainEvent '#{domainEventName}' which is not defined"
domainEvent = domainEventName, DomainEventClass, domainEventPayload
.saveDomainEvent domainEvent, =>
domainEvent, ->
publishDomainEvent: (domainEvent, callback=->) =>
.publishDomainEvent domainEvent, callback
_createDomainEvent: (domainEventName, DomainEventClass, domainEventPayload) ->
new DomainEvent
id: eventric.generateUid()
name: domainEventName
context:
payload: new DomainEventClass domainEventPayload
addStore: (storeName, StoreClass, storeOptions={}) ->
[storeName] =
Class: StoreClass
options: storeOptions
@
###*
* defineDomainEvent
*
* Context
*
*
* Adds a DomainEvent Class which will be used when emitting or handling DomainEvents inside of Aggregates, Projectionpr or ProcessManagers
*
* {String} domainEventName Name of the DomainEvent
* {Function} DomainEventClass DomainEventClass
###
defineDomainEvent: (domainEventName, DomainEventClass) ->
[domainEventName] = DomainEventClass
@
defineDomainEvents: (domainEventClassesObj) ->
domainEventName, DomainEventClass for domainEventName, DomainEventClass of domainEventClassesObj
@
###*
* addCommandHandler
*
* Context
*
*
* Use as: addCommandHandler(commandName, commandFunction)
*
* Add Commands to the `context`. These will be available to the `command` method after calling `initialize`.
*
*
```javascript
exampleContext.addCommandHandler('someCommand', function(params, callback) {
// ...
});
```
* {String} commandName Name of the command
*
* {String} commandFunction Gets `this.aggregate` dependency injected
* `this.aggregate.command(params)` Execute command on Aggregate
* * `params.name` Name of the Aggregate
* * `params.id` Id of the Aggregate
* * `params.methodName` MethodName inside the Aggregate
* * `params.methodParams` Array of params which the specified AggregateMethod will get as function signature using a [splat](http://stackoverflow.com/questions/6201657/what-does-splats-mean-in-the-coffeescript-tutorial)
*
* `this.aggregate.create(params)` Execute command on Aggregate
* * `params.name` Name of the Aggregate to be created
* * `params.props` Initial properties so be set on the Aggregate or handed to the Aggregates create() method
###
addCommandHandler: (commandHandlerName, commandHandlerFn) ->
[commandHandlerName] = =>
command =
id: eventric.generateUid()
name: commandHandlerName
params: arguments[0] ? null
_di = {}
for diFnName, diFn of
_di[diFnName] = diFn
repositoryCache = null
_di.$repository = (aggregateName) =>
if not repositoryCache
AggregateRoot = [aggregateName]
repository = new Repository
aggregateName: aggregateName
AggregateRoot: AggregateRoot
context: @
#repository.addMiddlewares
repositoryCache = repository
repositoryCache.setCommand command
#repository.setUser user
repositoryCache
commandHandlerFn.apply _di, arguments
@
addCommandHandlers: (commandObj) ->
commandHandlerName, commandFunction for commandHandlerName, commandFunction of commandObj
@
###*
* addQueryHandler
*
* Context
*
*
* Use as: addQueryHandler(queryHandler, queryFunction)
*
* Add Commands to the `context`. These will be available to the `query` method after calling `initialize`.
*
*
```javascript
exampleContext.addQueryHandler('SomeQuery', function(params, callback) {
// ...
});
```
* {String} queryHandler Name of the query
*
* {String} queryFunction Function to execute on query
###
addQueryHandler: (queryHandlerName, queryHandlerFn) ->
[queryHandlerName] = => queryHandlerFn.apply , arguments
@
addQueryHandlers: (commandObj) ->
queryHandlerName, queryFunction for queryHandlerName, queryFunction of commandObj
@
###*
* addAggregate
*
* Context
*
*
*
* Use as: addAggregate(aggregateName, aggregateDefinition)
*
* Add [Aggregates](https://github.com/efacilitation/eventric/wiki/BuildingBlocks#aggregateroot) to the `context`. It takes an AggregateDefinition as argument. The AggregateDefinition must at least consists of one AggregateRoot and can optionally have multiple named AggregateEntities. The Root and Entities itself are completely vanilla since eventric follows the philosophy that your DomainModel-Code should be technology-agnostic.
*
*
```javascript
exampleContext.addAggregate('Example', {
root: function(){
this.doSomething = function(description) {
// ...
}
},
entities: {
'ExampleEntityOne': function() {},
'ExampleEntityTwo': function() {}
}
});
```
*
* {String} aggregateName Name of the Aggregate
* {String} aggregateDefinition Definition containing root and entities
###
addAggregate: (aggregateName, AggregateRootClass) ->
[aggregateName] = AggregateRootClass
@
addAggregates: (aggregatesObj) ->
aggregateName, AggregateRootClass for aggregateName, AggregateRootClass of aggregatesObj
@
###*
* subscribeToDomainEvent
*
* Context
*
*
* Use as: subscribeToDomainEvent(domainEventName, domainEventHandlerFunction)
*
* Add handler function which gets called when a specific `DomainEvent` gets triggered
*
*
```javascript
exampleContext.subscribeToDomainEvent('Example:create', function(domainEvent) {
// ...
});
```
*
* {String} domainEventName Name of the `DomainEvent`
*
* {Function} Function which gets called with `domainEvent` as argument
* - `domainEvent` Instance of [[DomainEvent]]
*
###
subscribeToDomainEvent: (domainEventName, handlerFn, options = {}) ->
domainEventHandler = () => handlerFn.apply , arguments
.subscribeToDomainEvent domainEventName, domainEventHandler, options
@
###*
* subscribeToDomainEventWithAggregateId
*
* Context
*
###
subscribeToDomainEventWithAggregateId: (domainEventName, aggregateId, handlerFn, options = {}) ->
domainEventHandler = () => handlerFn.apply , arguments
.subscribeToDomainEventWithAggregateId domainEventName, aggregateId, domainEventHandler, options
subscribeToAllDomainEvents: (handlerFn, options = {}) ->
domainEventHandler = () => handlerFn.apply , arguments
.subscribeToAllDomainEvents domainEventHandler, options
subscribeToDomainEvents: (domainEventHandlersObj) ->
domainEventName, handlerFn for domainEventName, handlerFn of domainEventHandlersObj
@
###*
* addDomainService
*
* Context
*
*
* Use as: addDomainService(domainServiceName, domainServiceFunction)
*
* Add function which gets called when called using $domainService
*
*
```javascript
exampleContext.addDomainService('DoSomethingSpecial', function(params, callback) {
// ...
});
```
*
* {String} domainServiceName Name of the `DomainService`
*
* {Function} Function which gets called with params as argument
###
addDomainService: (domainServiceName, domainServiceFn) ->
[domainServiceName] = => domainServiceFn.apply , arguments
@
addDomainServices: (domainServiceObjs) ->
domainServiceName, domainServiceFn for domainServiceName, domainServiceFn of domainServiceObjs
@
###*
* addAdapter
*
* Context
*
*
* Use as: addAdapter(adapterName, AdapterClass)
*
* Add adapter which get can be used inside of `CommandHandlers`
*
*
```javascript
exampleContext.addAdapter('SomeAdapter', function() {
// ...
});
```
*
* {String} adapterName Name of Adapter
*
* {Function} Adapter Class
###
addAdapter: (adapterName, adapterClass) ->
[adapterName] = adapterClass
@
addAdapters: (adapterObj) ->
adapterName, fn for adapterName, fn of adapterObj
@
###*
* addProjection
*
* Context
*
*
* Add Projection that can subscribe to and handle DomainEvents
*
* {string} projectionName Name of the Projection
* {Function} The Projection Class definition
* - define `subscribeToDomainEvents` as Array of DomainEventName Strings
* - define handle Funtions for DomainEvents by convention: "handleDomainEventName"
###
addProjection: (projectionName, ProjectionClass) ->
.push
name: projectionName
class: ProjectionClass
@
addProjections: (viewsObj) ->
projectionName, ProjectionClass for projectionName, ProjectionClass of viewsObj
@
###*
* initialize
*
* Context
*
*
* Use as: initialize()
*
* Initializes the `context` after the `add*` Methods
*
*
```javascript
exampleContext.initialize(function() {
// ...
})
```
###
initialize: (callback=->) ->
new Promise (resolve, reject) =>
.debug "[#{@name}] Initializing"
.debug "[#{@name}] Initializing Store"
.then =>
.debug "[#{@name}] Finished initializing Store"
=
$adapter: => .apply @, arguments
$query: => .apply @, arguments
$domainService: =>
( arguments[0]).apply @, [arguments[1], arguments[2]]
$projectionStore: => .apply @, arguments
$emitDomainEvent: => .apply @, arguments
.debug "[#{@name}] Initializing Adapters"
.then =>
.debug "[#{@name}] Finished initializing Adapters"
.debug "[#{@name}] Initializing Projections"
.then =>
.debug "[#{@name}] Finished initializing Projections"
.debug "[#{@name}] Finished initializing"
= true
callback()
resolve()
.catch (err) ->
callback err
reject err
_initializeStores: ->
new Promise (resolve, reject) =>
stores = []
for storeName, store of (eventric.defaults , eventric.getStores())
stores.push
name: storeName
Class: store.Class
options: store.options
eventric.eachSeries stores, (store, next) =>
.debug "[#{@name}] Initializing Store #{store.name}"
[store.name] = new store.Class
[store.name].initialize @, store.options, =>
.debug "[#{@name}] Finished initializing Store #{store.name}"
next()
, (err) ->
return reject err if err
resolve()
_initializeProjections: ->
new Promise (resolve, reject) =>
eventric.eachSeries , (projection, next) =>
eventNames = null
projectionName = projection.name
.debug "[#{@name}] Initializing Projection #{projectionName}"
projectionService.initializeInstance projection, {}, @
.then (projectionId) =>
.debug "[#{@name}] Finished initializing Projection #{projectionName}"
next()
.catch (err) ->
reject err
, (err) =>
return reject err if err
resolve()
_initializeAdapters: ->
new Promise (resolve, reject) =>
for adapterName, adapterClass of
adapter = new [adapterName]
adapter.initialize?()
[adapterName] = adapter
resolve()
###*
* getProjection
*
* Context
*
* Get a Projection Instance after initialize()
*
* {String} projectionName Name of the Projection
###
getProjection: (projectionId) ->
projectionService.getInstance projectionId
###*
* getAdapter
*
* Context
*
* Get a Adapter Instance after initialize()
*
* {String} adapterName Name of the Adapter
###
getAdapter: (adapterName) ->
[adapterName]
###*
* getDomainEvent
*
* Context
*
* Get a DomainEvent Class after initialize()
*
* {String} domainEventName Name of the DomainEvent
###
getDomainEvent: (domainEventName) ->
[domainEventName]
###*
* getDomainService
*
* Context
*
* Get a DomainService after initialize()
*
* {String} domainServiceName Name of the DomainService
###
getDomainService: (domainServiceName) ->
[domainServiceName]
###*
* getDomainEventsStore
*
* Context
*
* Get the DomainEventsStore after initialization
###
getDomainEventsStore: ->
storeName = 'default domain events store'
[storeName]
saveDomainEvent: (domainEvent) ->
new Promise (resolve, reject) =>
.saveDomainEvent domainEvent, (err, events) =>
domainEvent
return reject err if err
resolve events
findAllDomainEvents: ->
new Promise (resolve, reject) =>
.findAllDomainEvents (err, events) ->
return reject err if err
resolve events
findDomainEventsByName: (findArguments...) ->
new Promise (resolve, reject) =>
.findDomainEventsByName findArguments..., (err, events) ->
return reject err if err
resolve events
findDomainEventsByNameAndAggregateId: (findArguments...) ->
new Promise (resolve, reject) =>
.findDomainEventsByNameAndAggregateId findArguments..., (err, events) ->
return reject err if err
resolve events
findDomainEventsByAggregateId: (findArguments...) ->
new Promise (resolve, reject) =>
.findDomainEventsByAggregateId findArguments..., (err, events) ->
return reject err if err
resolve events
findDomainEventsByAggregateName: (findArguments...) ->
new Promise (resolve, reject) =>
.findDomainEventsByAggregateName findArguments..., (err, events) ->
return reject err if err
resolve events
getProjectionStore: (storeName, projectionName, callback) =>
new Promise (resolve, reject) =>
if not [storeName]
err = "Requested Store with name #{storeName} not found"
.error err
callback? err, null
return reject err
[storeName].getProjectionStore projectionName, (err, projectionStore) =>
callback? err, projectionStore
return reject err if err
resolve projectionStore
clearProjectionStore: (storeName, projectionName, callback) =>
new Promise (resolve, reject) =>
if not [storeName]
err = "Requested Store with name #{storeName} not found"
.error err
callback? err, null
return reject err
[storeName].clearProjectionStore projectionName, (err, done) =>
callback? err, done
return reject err if err
resolve done
###*
* getEventBus
*
* Context
*
* Get the EventBus after initialization
###
getEventBus: ->
###*
* command
*
* Context
*
*
*
* Use as: command(command, callback)
*
* Execute previously added `commands`
*
*
```javascript
exampleContext.command('doSomething',
function(err, result) {
// callback
});
```
*
* {String} `commandName` Name of the CommandHandler to be executed
* {Object} `commandParams` Parameters for the CommandHandler function
* {Function} callback Gets called after the command got executed with the arguments:
* - `err` null if successful
* - `result` Set by the `command`
###
command: (commandName, commandParams) ->
.debug 'Got Command', commandName
new Promise (resolve, reject) =>
if not
err = 'Context not initialized yet'
.error err
err = new Error err
return reject err
if [commandName]
[commandName] commandParams, (err, result) =>
.debug 'Completed Command', commandName
eventric.nextTick =>
if err
reject err
else
resolve result
else
err = "Given command #{commandName} not registered on context"
.error err
err = new Error err
reject err
###*
* query
*
* Context
*
*
*
* Use as: query(query, callback)
*
* Execute previously added `QueryHandler`
*
*
```javascript
exampleContext.query('Example', {
foo: 'bar'
}
},
function(err, result) {
// callback
});
```
*
* {String} `queryName` Name of the QueryHandler to be executed
* {Object} `queryParams` Parameters for the QueryHandler function
* {Function} `callback` Callback which gets called after query
* - `err` null if successful
* - `result` Set by the `query`
###
query: (queryName, queryParams) ->
.debug 'Got Query', queryName
new Promise (resolve, reject) =>
if not
err = 'Context not initialized yet'
.error err
err = new Error err
reject err
return
if [queryName]
[queryName] queryParams, (err, result) =>
.debug 'Completed Query', queryName
eventric.nextTick =>
if err
reject err
else
resolve result
else
err = "Given query #{queryName} not registered on context"
.error err
err = new Error err
reject err
enableWaitingMode: ->
'waiting mode', true
disableWaitingMode: ->
'waiting mode', false
isWaitingModeEnabled: ->
'waiting mode'
module.exports = Context