base-domain
Version:
simple module to help build Domain-Driven Design
347 lines (253 loc) • 8.29 kB
text/coffeescript
'use strict'
Base = require './base'
Entity = require './entity'
GeneralFactory = require './general-factory'
{ isPromise } = require('../util')
###*
Base repository class of DDD pattern.
Responsible for perpetuation of models.
BaseRepository has a client, which access to data resource (RDB, NoSQL, memory, etc...)
the parent "Base" class just simply gives a @getFacade() method.
@class BaseRepository
@extends Base
@module base-domain
###
class BaseRepository extends Base
###*
model name to handle
@property modelName
@static
@protected
@type String
###
@modelName: null
getModelName: ->
@constructor.modelName ? @constructor.getName().slice(0, -'-repository'.length)
###*
client accessing to data resource (RDB, NoSQL, memory, etc...)
mock object is input by default.
Extenders must set this property to achieve perpetuation
@property client
@abstract
@protected
@type ResourceClientInterface
###
client: null
###*
constructor
@constructor
@params {RootInterface} root
@return
###
constructor: (root) ->
super(root)
modelName = @getModelName()
###*
factory of the entity.
@property {FactoryInterface} factory
###
@factory = GeneralFactory.create(modelName, @root)
if (@factory.getModelClass()::) not instanceof Entity
@error('base-domain:repositoryWithNonEntity', "cannot define repository to non-entity: '#{modelName}'")
###*
get model class this factory handles
@method getModelClass
@return {Class}
###
getModelClass: ->
@factory.getModelClass()
###*
returns Promise or the result of given function
@return {any}
@protected
###
resolve: (result, fn) ->
if isPromise result
return result.then (obj) => fn.call(@, obj)
else
return fn.call(@, result)
###*
Update or insert a model instance
@method save
@public
@param {Entity|Object} entity
@param {Object} [options]
@param {ResourceClientInterface} [options.client=@client]
@return {Entity|Promise(Entity)} entity (the same instance from input, if entity given,)
###
save: (entity, options = {}) ->
{ client } = options
delete options.client
if entity not instanceof Entity
entity = @factory.createFromObject entity, options
client ?= @client
# set "createdAt-compatible property when id is not set
data = entity.toPlainObject()
@appendTimeStamp(data)
method =
switch options.method
when 'upsert', 'create'
options.method
else
'upsert'
@resolve client[method](data), (obj) ->
newEntity = @createFromResult(obj, options)
entity.inherit newEntity
###*
get entity by id.
@method get
@public
@param {String|Number} id
@param {Object} [options]
@param {ResourceClientInterface} [options.client=@client]
@return {Entity|Promise(Entity)} entity
###
get: (id, options = {}) ->
{ client } = options
delete options.client
client ?= @client
@resolve client.findById(id), (obj) ->
@createFromResult(obj, options)
###*
alias for get()
@method getById
@public
@param {String|Number} id
@param {Object} [options]
@param {ResourceClientInterface} [options.client=@client]
@return {Entity|Promise(Entity)} entity
###
getById: (id, options) ->
@get(id, options)
###*
get entities by id.
@method getByIds
@public
@param {Array|(String|Number)} ids
@param {Object} [options]
@param {ResourceClientInterface} [options.client=@client]
@return {Array(Entity)|Promise(Array(Entity))} entities
###
getByIds: (ids, options) ->
results = (@get(id, options) for id in ids)
existence = (val) -> val?
if isPromise results[0]
return Promise.all(results).then (models) -> models.filter existence
else
return results.filter existence
###*
get all entities
@method getAll
@return {Array(Entity)|Promise(Array(Entity))} array of entities
###
getAll: ->
@query({})
###*
Find all model instances that match params
@method query
@public
@param {Object} [params] query parameters
@param {Object} [options]
@param {ResourceClientInterface} [options.client=@client]
@return {Array(Entity)|Promise(Array(Entity))} array of entities
###
query: (params, options = {}) ->
{ client } = options
delete options.client
client ?= @client
@resolve client.find(params), (objs) ->
@createFromQueryResults(params, objs, options)
###*
Find one model instance that matches params, Same as query, but limited to one result
@method singleQuery
@public
@param {Object} [params] query parameters
@param {Object} [options]
@param {ResourceClientInterface} [options.client=@client]
@return {Entity|Promise(Entity)} entity
###
singleQuery: (params, options = {}) ->
{ client } = options
delete options.client
client ?= @client
@resolve client.findOne(params), (obj) ->
return @createFromResult(obj, options)
###*
Destroy the given entity (which must have "id" value)
@method delete
@public
@param {Entity} entity
@param {Object} [options]
@param {ResourceClientInterface} [options.client=@client]
@return {Boolean|Promise(Boolean)} isDeleted
###
delete: (entity, options = {}) ->
{ client } = options
delete options.client
client ?= @client
@resolve client.destroy(entity), ->
return true
###*
Update set of attributes.
@method update
@public
@param {String|Number} id id of the entity to update
@param {Object} data key-value pair to update (notice: this must not be instance of Entity)
@param {Object} [options]
@param {ResourceClientInterface} [options.client=@client]
@return {Entity|Promise(Entity)} updated entity
###
update: (id, data, options = {}) ->
{ client } = options
delete options.client
if data instanceof Entity
throw @error 'base-domain:updateWithModelInhihited', """
update entity with BaseRepository#update() is not allowed.
use BaseRepository#save(entity) instead
"""
client ?= @client
@appendTimeStamp(data, isUpdate = true)
@resolve client.updateAttributes(id, data), (obj) ->
return @createFromResult(obj, options)
###*
add createdAt, updatedAt to given data
- createdAt will not be overriden if already set.
- updatedAt will be overriden for each time
@method appendTimeStamp
@protected
@param {Object} data
@param {Boolean} isUpdate true when updating
@return {Object} data
###
appendTimeStamp: (data, isUpdate = false) ->
modelProps = @getFacade().getModelProps(@getModelName())
propCreatedAt = modelProps.createdAt
propUpdatedAt = modelProps.updatedAt
now = new Date().toISOString()
if propCreatedAt and not isUpdate
data[propCreatedAt] ?= now
if propUpdatedAt
data[propUpdatedAt] = now
return data
###*
Create model instance from result from client
@method createFromResult
@protected
@param {Object} obj
@param {Object} [options]
@return {BaseModel} model
###
createFromResult: (obj, options) ->
@factory.createFromObject(obj, options)
###*
Create model instances from query results
@method createFromQueryResults
@protected
@param {Object} params
@param {Array(Object)} objs
@param {Object} [options]
@return {Array(BaseModel)} models
###
createFromQueryResults: (params, objs, options) ->
(@createFromResult(obj, options) for obj in objs)
module.exports = BaseRepository