@ebay/oja-action
v2.0.7
Published
Dependency injection via actions
Downloads
10
Readme
oja-action
This module is a subset of eBay Oja framework.
The module defines Oja Action Component API and provides an action discovery mechanism for Node.JS applications.
Installation
$ npm install @ebay/oja-action --save
Usage
Action definition
- folder structure:
module-root/
action.json
action.js
- action.json:
{
"MATH/sum": "./action"
}
- action.js
module.exports = context => (a, b) => a + b;
Calling action
const { createContext } = require('@ebay/oja-action');
// context creation can be called for every new flow
const context = await createContext();
// calling action can be done many times within the same context
console.log(await context.action('MATH/sum', 1, 2)); // >> 3
console.log(await context.action('MATH/sum', 5, 2)); // >> 7
Action execution logic
MATH/inc example:
// action.js
module.exports = context => {
// init state per context if needed
let count = 0;
console.log('createContext has been called');
return () => {
// action main logic
return count++;
}
};
// execution.js
const context = await createContext(); // >> createContext has been called
console.log(await context.action('MATH/inc')); // >> 0
console.log(await context.action('MATH/inc')); // >> 1
console.log(await context.action('MATH/inc')); // >> 2
Action component definition
The action component may define more than one action.
The actions can be declared in action.json file as follows:
{
"NAMESPACE1/ACTION-NAME1": "path:src/action1",
"NAMESPACE1/ACTION-NAME2": "path:src/action2",
...
"NAMESPACE2": {
"ACTION-NAME3": "path:path/to/actions3",
"ACTION-NAME4": "path:path/to/actions4"
}
}
The action.json must be placed into the action folder or into the root for the module or application to let the framework to discover the actions.
The action packaged into the external modules should be declared in the direct dependencies in the caller application/module package.json.
The action.json in the root folder may have locations where to search for the actions using relative resolution:
[
"src/actions",
"src/controllers"
]
The above is useful to tell Oja framework where to look for the actions in the application.
Here's more variations of action.json file:
- action with selectors:
{
"MATH/sum": {
"function": "./action",
"version": "1.0.0",
"env": "test"
}
}
- group of actions:
{
"MATH": {
"sum": "./action-sum",
"mul": "./action-mul"
}
}
- group of actions with selectors:
{
"MATH": {
"sum": {
"function": "./action-sum",
"env": "test"
},
"mul": {
"function": "./action-mul",
"env": "test"
}
}
}
The action can be called as follows:
context.action('NAMESPACE/foo')
Action discovery mechanism
The discovery of actions accessible for the caller starts from the calling point of the specific action. The caller location is used as a starting point to find the root of the module or an application and then use their dependencies as well as action.json at the root, if any, to find available actions.
In case multiple actions define the same namespace/function, the earlier discovered actions will be the first in line for matching. A warning will be produced to notify the developer about the possible conflict when two exact actions are found.
Action selectors
The matching mechanism allows the use of selectors that help the action resolver pick a more specific action.
The framework by default attaches the following selectors to every action function:
- module is a module or app name where the action was found
- version is a module or app version where the action was found
- namespace is a namespace of the action
One example where defining duplicate actions can be useful is unit tests. The developers can create mock actions with "env": "test" selector, for example, and then use test
selectors during context creation in the unit tests.
Example
- /actions/foo/action.json (real):
{
"NAMESPACE/foo": {
"function": "index",
"env": "production"
}
}
- /mocks/foo/actions.json:
{
"NAMESPACE/foo": {
"function": "mock-index",
"env": "test"
}
}
- creating context in production:
// enforcing common selectors for all actions
// during context creation
const context = createContext({
selectors: {
// note: using '~' allows to drop selector
// v---- when matching action is not found
'~env': 'production'
}
});
- creating context during tests:
// enforcing common selectors for all actions
// during context creation
const context = createContext({
selectors: {
// note: using '~' allows to drop selector
// v---- when matching action is not found
'~env': 'test'
}
});
- calling action:
// this will try to find action with env:test attribute
// and if not found, it will drop env selector and dot the search again
context.action('NAMESPACE/foo');
// or you can enforce some selectors per action call
context.action({
name: 'NAMESPACE/foo',
foo: 'foov', // << find only actions that have foo=foov
'~bar': 'barv' // << if not found, drop this selector.
});
Note: In some cases developers might skip creating mock versions for some actions but still be able to use a specific selectors for those actions that have the mock versions. In this case the developer can use ~
prefix for the selectors that can be dropped during action resolution when the exact match is not found.
The droppable selectors while matching will be dropped one by one starting from the end, i.e. the selector order is important.