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"
  }
}

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')
})