toki was conceived as configuration based rule engine where your configuration dictates how your routes are composed and executed by different steps.
let's take a look at the different pieces that make a toki configuration.
{
"routes": [
{
"path" : "/products/{id}",
"httpAction": "GET",
"actions" : [
{
"name": "product",
"type": "toki-method-product-lookup"
},
{
"name": "inventory",
"type": "toki-method-inventory-lookup"
}
],
"failure" : [
{
"name": "rollback",
"type": "toki-method-rollback"
}
]
}
]
}
An array of route objects.
-
url
- partial http url including params in the style of toki-bridge being used.url : 'product/{id}'
-
httpAction
- http verb to be used on the route. allowed values 'GET', 'POST', 'PUT', 'DELETE', 'PATCH'. -
tags
- TODO -
description
- a description of your route purpose in case you forget it. -
actions
- array of actions.
{
"routes": [
{
"path" : "/products/{id}",
"httpAction": "GET",
"description" : "Lookup up products by id",
"actions" : [
{
"name": "product",
"type": "toki-method-product-lookup"
}
]
},
{
"path" : "/products/",
"httpAction": "POST",
"description" : "Create a product",
"actions" : [
{
"name": "product",
"type": "toki-method-product-create"
}
]
}
]
}
An array of action rules to be executed in sequential order.
{
"routes": [
{
"path" : "/products/{id}",
"httpAction": "GET",
"actions" : [
{
"name": "product",
"type": "toki-method-product-lookup",
"description" : "lookup product catalog"
},
{
"name": "inventory",
"type": "toki-method-inventory-lookup",
"description" : "lookup product inventory"
},
{
"name": "backorder",
"type": "toki-method-product-backorder",
"description" : "check if product inventory is below limit and initiate backorder"
},
{
"name": "map",
"type": "toki-method-product-lookup",
"description" : "compose reponse payload with product description and inventory"
}
]
}
]
}
name
- name of the action to be executed. action name will be used to bind the action response to the execution context, that way subsequent actions will be able to use the previos action response, say what? let's take a deep breath and try to explain this nonsense:
{
"routes": [
{
"path" : "/products/{id}",
"httpAction": "GET",
"actions" : [
{
"name": "product",
"type": "toki-method-product-lookup",
"description" : "lookup product catalog",
"inputConfiguration" : {
"url" : "http://product/"
}
},
{
"name": "inventory",
"type": "toki-method-inventory-lookup",
"description" : "lookup product inventory",
"inputConfiguration" : {
"url" : "http://inventory/",
"locations" : ["East", "West", "Central"]
}
},
{
"name": "backorder",
"type": "toki-method-product-backorder",
"description" : "check if product inventory is below limit and initiate backorder"
},
{
"name": "map",
"type": "toki-method-product-lookup",
"description" : "compose reponse payload with product description and inventory"
}
]
}
]
}
- action
product
executes result gets bound into the execution context asthis['product'] = result
. - action
inventory
executes and has access tothis['product']
, it's own result gets bounded to execution context asthis['inventory']
. - action
backorder
executes and has access tothis['product']
andthis['inventory']
, it's own result gets bounded to execution context asthis['backorder']
. - action
map
executes and access all previous resultsthis['product']
,this['inventory']
,this['backorder']
, and combines them into an new object that will be passed tothis.resppnse(newObj)
to be sent back to a happy client.
type
- this determines the action handler node module to be required and invoked as part of the execution.
type
is free form but we suggest our standard naming convention of pre-appending toki-method-
to your action handler name.
{
"routes": [
{
"path" : "/products/{id}",
"httpAction": "GET",
"actions" : [
{
"name": "product",
"type": "toki-method-product-lookup",
"description" : "lookup product catalog",
"options" : {
"url" : "http://product/"
}
}
]
}
]
}
On the previous configuration toki-method-product-lookup
will be invoked as require('toki-method-product-lookup')
-
description
- write something nice about your action. -
options
- optional. Configuration object to be passed to the action executor.
if your action is an array of action objects, those will be executed in parallel and wait for all actions to finish before continuing with the next action in the execution process.
Clear as mud right? this code example will be crystal clear to our nerdy readers:
{
"routes": [
{
"path" : "/products/{id}",
"httpAction": "GET",
"actions" : [
{
"name": "product",
"type": "toki-method-product-lookup",
"description" : "lookup product catalog"
},
[
{
"name": "inventory-central",
"type": "toki-method-inventory-lookup-location-central",
"description" : "lookup product inventory in central region"
},
{
"name": "inventory-east",
"type": "toki-method-inventory-lookup-location-east",
"description" : "lookup product inventory in east region"
},
{
"name": "inventory-west",
"type": "toki-method-inventory-lookup-location-west",
"description" : "lookup product inventory in west region"
}
],
{
"name": "backorder",
"type": "toki-method-product-backorder",
"description" : "check if product inventory is below limit and initiate backorder"
},
{
"name": "map",
"type": "toki-method-product-map",
"description" : "compose reponse payload with product description and inventory"
}
]
}
]
}
- action
product
executes. - actions
inventory-central
,inventory-easr
,inventory-west
execute in parallel and wait for all 3 actions fo finish execution. - action
backorder
executes. - action
map
executes.
An array of action rules to be executed in sequential order. These failure rules are executed if one or more rule from the "actions"
ruleset error out.
The failure rules are executed identically the same as action rules, with the sole exception being that an additional property errors
is added to the parameter passed to the toki-method handler.
An error in the "failure"
ruleset will result in a 500 Server Error response being sent.
{
"routes": [
{
"path" : "/products/{id}",
"httpAction": "GET",
"actions" : [
{
"name": "product",
"type": "toki-method-product-lookup",
"description" : "lookup product catalog"
},
{
"name": "inventory",
"type": "toki-method-inventory-lookup",
"description" : "lookup product inventory"
},
{
"name": "backorder",
"type": "toki-method-product-backorder",
"description" : "check if product inventory is below limit and initiate backorder"
},
{
"name": "map",
"type": "toki-method-product-lookup",
"description" : "compose reponse payload with product description and inventory"
}
],
"failure" : [
{
"name": "rollback",
"type": "toki-method-rollback",
"description": "roll back any changes triggered in unsuccessful 'actions'"
}
]
}
]
}
toki uses the following joi schema to validate the configuration returned by toki-config. Better get your configuration right or toki will throw a very friendly joi error.
const action = Joi.object().keys({
name : Joi.string(),
type : Joi.string(),
description: Joi.string().optional().allow(null, ''),
options : Joi.object().optional().allow(null)
});
const actions = Joi.array().items(action).min(2);
const routes = Joi.object().keys({
path : Joi.string(),
httpAction : Joi.string().valid('GET', 'POST', 'PUT', 'DELETE', 'PATCH'),
tags : Joi.array().items(Joi.string()).min(1).optional(),
description: Joi.string().optional().allow(null, ''),
actions : Joi.array().items(action, actions).min(1),
failure : Joi.array().items(action, actions).min(1).optional()
});
const schema = Joi.object().keys({
routes: Joi.array().items(routes).min(1)
}).label('toki configuration');
Most features available to methods exist in the context of that action. this
will refer to your context. The context looks as such:
{
server: {
request //http request defined above and by the bridge,
response //http response defined above and by the bridge
},
action //the configuration block for the action
contexts: { //This is a pathway to allow you to see the contexts of other actions
other-action-1,
other action-2
}
}
An example configuration block available under this.action
:
{
"name": "inventory-central",
"type": "toki-method-inventory-lookup-location-central",
"description": "lookup product inventory in central region",
"inputConfiguration": {
//your input config options go here
},
"clientResponseConfiguration": {
//config for shaping your response go here
}
}
A method it's just a fancy name for a node module that exports a function that either returns a value or a promise that fulfills into a value, simple as that. When that method is invoked in a flow config by an action, that particular instance of it is called an "action handler".
Our standard naming convention for a method is prefixing it with toki-method-*
such as toki-method-http
or toki-method-proxy
.
Your action handler won't be "newable", but it will be bound to a new context every time it's called as defined in the above section.
Any exceptions you throw will be handled gracefully by toki.
Sample config:
{
"routes": [
{
"path" : "/products/{id}",
"httpAction": "GET",
"actions" : [
{
"name": "product",
"type": "toki-method-product-lookup",
"description" : "lookup product catalog",
"inputConfiguration" : {
"url" : "http://product/"
}
}
]
}
]
}
toki-method-product-lookup
module will be required as
//require without instantiation
const actionHandler = require('toki-method-product-lookup');
module.exports = function(context) {
//access request header
const headers = context.request.headers;
//do your thing;
//write something to the response object
context.response({
key : 'value'
});
//and yet return something for next action to enjoy
return {
key : 'value'
};
}
//we all love bluebird
const Promise = require('bluebird');
module.exports = function(context) {
//clone context and create my own so I can add my stuff
const newContext = Object.assign({}, context);
return Promise.resolve()
.bind(newContext)
.then(someAsyncStuff)
.then((result)=>{
return result;
});
}
When fulfilling a route request toki will guard against uncuaght errors and will fail gracefuly sending a BOOM 500 error.
As a general rule avoid handling errors in your action handler unless that's part of your business logic, don't turn down the freebie toki gives you/