Write your own mixin #
TL-DR #
- create a js file what exports 2 function
module.exports = {
initDomain: null,
initEntity: initEntityForMyMixin
}
/**
* My mixin descriptin
* @param {UBEntity} entity Entity for initialization
* @param {UBEntityMixin} mixinCfg Mixin configuration from entity metafile
*/
function initEntityForTestMixin(entity, mixinCfg) {
// verify mixinCfg
// add methods / events etc for entity
}
- in model entrypoint register a mixin
const UB = require('@unitybase/ub')
const myMixinImpl = require('./myMixin.js')
UB.registerMixinModule('myMixin', myMixinImpl)
- now new mixin can be used in entity *.meta file as such
"mixins": {
"myMixin": {
"mixinCfgParam": "param value"
}
}
Enabling\disabling mixins #
Each mixin can be implicitly enable on domain level for all entities by adding it code into domain.implicitlyAddedMixins
array in ubConfig. This is how audit
mixin is adding and multitenancy
(in case security.multitenancy.enabled: true
).
If implicitlyAddedMixins
is defined in config, audit
should be added into array to be enabled
For individual entity any mixin can be implicitly disabled by specifying enabled: false
in entity meta file. For example,
to disable an audit
mixin in entity.meta:
{
"mixins": {
"audit": {
"enabled": false
}
}
}
Implementation tips #
Adding new attributes/entities #
Sometimes mixin should add a new attributes\entities into domain. This can be done using metadata transformation hook what can mutate a Domain JSON.
About metadata hooks see Application initialization section
of @unitybase/ub
module documentation
Metadata transformation hook is called on the domain loading stage while server is in
single-thread
mode.In opposite mixin module is evaluated for each working thread
The real-life code what mutates a Domain and adds additional attributes can be found in [@unitybase/ub] model - see sources @unitybase/ub/_hookMetadataTransformation.js
Logging with log level #
Wrap methods in logEnter / logLeave block to get a better logging + time profile for free.
Since @unitybase/ub@5.22.39 there is a service function App.wrapEnterLeaveForUbMethod
const App = require('@unitybase/ub').App
// use pattern below for method enter text - the same as native method do
entytModule.select = App.wrapEnterLeaveForUbMethod(`method(${MIXIN_NAME}) ${entity.name}.select`, myMixinSelect)
entityModule.entity.addMethod('select')
function myMixinSelect(cxt) {
console.debug('some debug (shifted by recursion level automatically)')
}
Before 5.33.39 use a polyfill
function wrapEnterLeave (enterText, methodImpl) {
return function enterLeave(ctx) {
App.logEnter(enterText)
try {
methodImpl(ctx)
} finally {
App.logLeave()
}
}
}
The logging will be (first and last lines are added by App.logEnter / App.logLeave, all console.* output from inside select
method
are shifted by recursion level)
20210314 09224807 " + method(myMixin) my_entity.select
20210314 09224807 " debug some debug (shifted by recursion level automatically)
20210314 09224807 " - 00.005.124
Events order #
Mixin's initialization are executed AFTER all models entry point is evaluated, so if some model code adds an event handler
// my_entity.js
me.on('insert:before', function myEntityInsertBefore(ctx) {
console.debug('my_entity insert:before')
})
handler insert:before
, added by mixin entity initialization function will be executed after myEntityInsertBefore
.
To add an event handler what executed BEFORE model handlers use a prependListener
instead of on
(or even both ot them if needed)
// myMixin.js
function initEntityForMyMixin(entity, mixinCfg) {
/** @type {EntityNamespace} */
const entityModule = global[entity.name]
function myMixinInsert(ctx) {
console.debug('insert called')
}
entityModule.insert = wrapEnterLeave(`method(${MIXIN_NAME}) ${entity.name}.insert`, myMixinInsert)
entityModule.entity.addMethod('insert')
entityModule.prependListener('insert:before', (ctx) => {
console.debug('myMixin insert:before (called BEFORE myEntityInsertBefore)')
})
entityModule.on('insert:before', (ctx) => {
console.debug('myMixin insert:before (called AFTER myEntityInsertBefore)')
})
}
in the sample above execution of insert
method produce such log output:
20210314 09224807 " + ubql?rq=my_entity.insert&uitag=frm-my_entity
20210314 09224807 " http 127.0.0.1 -> POST ubql?rq=my_entity.insert&uitag=frm-my_entity
20210314 09224807 " debug myMixin insert:before (called BEFORE myEntityInsertBefore)
20210314 09224807 " debug my_entity insert:before
20210314 09224807 " debug myMixin insert:before (called AFTER myEntityInsertBefore)
20210314 09224807 " + method(myMixin) my_entity.insert
20210314 09224807 " debug some debug (shifted by recursion level automatically)
20210314 09224807 " - 00.005.124
20210314 09224807 " + method(TubAuditMixin) my_entity.afterinsert
.....
20210314 09224807 " - 00.000.656
20210314 09224807 " DB Commit connection "main" in 123us
20210314 09224807 " http 127.0.0.1 <- 200
20210314 09224807 " - 00.009.560
Debugging log performance #
In production mode Debug
log level is usually disable, so for debug output prefer
console.debug('some debug', i) // fast in case debug log level is off
instead of template string
console.debug(`some debug ${i}`) // calculate a string from template before pass it as argument even if debug is off
Debugging experience #
For better debugging experience try to avoid anonymous functions and lodash. Most Lodash functions creates a deep call stack. Anonymous functions are shown with pure names in call stack.
// my_entity.js
me.on('insert:before', function myEntityInsertBefore(ctx) {
console.debug('my_entity insert:before')
})