eventric
Version:
behavior-first application development
191 lines (142 loc) • 5.38 kB
text/coffeescript
class Eventric
constructor: ->
@PubSub = require './pub_sub'
@EventBus = require './event_bus'
@Remote = require './remote'
@Context = require './context'
@DomainEvent = require './domain_event'
@Aggregate = require './aggregate'
@Repository = require './repository'
@Projection = require './projection'
@Logger = require './logger'
@RemoteInMemory = require './remote/inmemory'
@StoreInMemory = require './store/inmemory'
@log = @Logger
@_contexts = {}
@_params = {}
@_domainEventHandlers = {}
@_domainEventHandlersAll = []
@_storeClasses = {}
@_remoteEndpoints = []
@addRemoteEndpoint 'inmemory', @RemoteInMemory.endpoint
@addStore 'inmemory', @StoreInMemory
@set 'default domain events store', 'inmemory'
set: (key, value) ->
@_params[key] = value
get: (key) ->
if not key
@_params
else
@_params[key]
addStore: (storeName, StoreClass, storeOptions={}) ->
@_storeClasses[storeName] =
Class: StoreClass
options: storeOptions
getStores: ->
@_storeClasses
context: (name) ->
if !name
err = 'Contexts must have a name'
@log.error err
throw new Error err
pubsub = new @PubSub
context = new @Context name, @
@mixin context, pubsub
@_delegateAllDomainEventsToGlobalHandlers context
@_delegateAllDomainEventsToRemoteEndpoints context
@_contexts[name] = context
context
getContext: (name) ->
@_contexts[name]
remote: (contextName) ->
if !contextName
err = 'Missing context name'
@log.error err
throw new Error err
pubsub = new @PubSub
remote = new @Remote contextName, @
@mixin remote, pubsub
remote
addRemoteEndpoint: (remoteName, remoteEndpoint) ->
@_remoteEndpoints.push remoteEndpoint
remoteEndpoint.setRPCHandler @_handleRemoteRPCRequest
_handleRemoteRPCRequest: (request, callback) =>
context = @getContext request.contextName
if not context
error = "Tried to handle Remote RPC with not registered context #{request.contextName}"
@log.error error
return callback error, null
if @Remote.ALLOWED_RPC_OPERATIONS.indexOf(request.method) is -1
error = "RPC operation '#{request.method}' not allowed"
@log.error error
return callback error, null
if request.method not of context
error = "Remote RPC method #{request.method} not found on Context #{request.contextName}"
@log.error error
return callback error, null
#middleware(request, user)
context[request.method] request.params...
.then (result) ->
callback null, result
.catch (error) ->
callback error
_delegateAllDomainEventsToGlobalHandlers: (context) ->
context.subscribeToAllDomainEvents (domainEvent) =>
eventHandlers = @getDomainEventHandlers context.name, domainEvent.name
for eventHandler in eventHandlers
eventHandler domainEvent
_delegateAllDomainEventsToRemoteEndpoints: (context) ->
context.subscribeToAllDomainEvents (domainEvent) =>
@_remoteEndpoints.forEach (remoteEndpoint) ->
remoteEndpoint.publish context.name, domainEvent.name, domainEvent
if domainEvent.aggregate
remoteEndpoint.publish context.name, domainEvent.name, domainEvent.aggregate.id, domainEvent
subscribeToDomainEvent: ([contextName, eventName]..., eventHandler) ->
contextName ?= 'all'
eventName ?= 'all'
if contextName is 'all' and eventName is 'all'
@_domainEventHandlersAll.push eventHandler
else
@_domainEventHandlers[contextName] ?= {}
@_domainEventHandlers[contextName][eventName] ?= []
@_domainEventHandlers[contextName][eventName].push eventHandler
getDomainEventHandlers: (contextName, domainEventName) ->
[].concat (@_domainEventHandlers[contextName]?[domainEventName] ? []),
(@_domainEventHandlers[contextName]?.all ? []),
(@_domainEventHandlersAll ? [])
generateUid: (separator) ->
# http://stackoverflow.com/a/12223573
S4 = ->
(((1 + Math.random()) * 0x10000) | 0).toString(16).substring 1
delim = separator or "-"
S4() + S4() + delim + S4() + delim + S4() + delim + S4() + delim + S4() + S4() + S4()
# TODO: Use existing npm module
defaults: (options, optionDefaults) ->
allKeys = [].concat (Object.keys options), (Object.keys optionDefaults)
for key in allKeys when !options[key] and optionDefaults[key]
options[key] = optionDefaults[key]
options
# TODO: Use existing npm module
eachSeries: (arr, iterator, callback) ->
# MIT https://github.com/jb55/async-each-series
callback = callback or ->
return callback() if not Array.isArray(arr) or not arr.length
completed = 0
iterate = ->
iterator arr[completed], (err) ->
if err
callback err
else
++completed
if completed >= arr.length
callback()
else
iterate()
return
return
iterate()
# TODO: Use existing npm module
mixin: (destination, source) ->
for prop of source
destination[prop] = source[prop]
module.exports = Eventric