shape-api-utils
v0.1.0
Published
utils to orchestrate data API requests
Downloads
3
Readme
##API orchestration module (AOM)
1 - AOM provides following method to be called from within the request handling chain of an Express server:
orchestrateApis(opts, callback, objs)
- This passes control to a
main
method in an application-specific orchestration module, or returns an error to thecallback
method if no application-specific orchestration module can be found. - Argument
opts
is cloned and then passed into themain
method. It is expected to contain:appOrchModule
: a resolvable path to an application-specific orchestration moduleapiOptions
: a json configuration object containingrequest
options for each API callreqParams
: an object with request params (a reference to Express'req.params
)reqQuery
: an object with querystring values (a reference to Express'req.query
)contextProp
: the name of a context property, to be used as container for derived API response values (seemergeResultWithDerived
below)
- Argument
callback
is expected to be a callback function which will be called when the orchestration finishes. - Argument
objs
is optional, it can be used to pass objects that should not be cloned, like class instances.
- This passes control to a
2 - AOM provides following methods for application's orchestration code:
replaceUriVars(apiOptions, uriVars)
- takes a uri (
apiOptions.uri
) with placeholder variables and replaces those placeholders with actual values. - Argument
apiOptions
: options to be passed in to the request module; the method will modify theuri
property of this object - Argument
uriVars
: an object containing all uri placeholders and their values
- takes a uri (
getApiData(apiOptions, uriVars, callback)
- makes an API call, using the
request
module and returns a callback with(err, result)
signature, and standardizes error handling/messaging. - Argument
apiOptions
: options to be passed in to the request module: it should contain at least auri
property which may have variable placeholders enclosed in curly braces ({myVar}
), and may contain optionalheaders
that will be passed to the API request - Argument
uriVars
: object with replacement values for the uri placeholders
- makes an API call, using the
getErrorResponse(err, body)
- returns a standardized error response object (proper response status still needs to be handled by the server)
- Argument
err
: an error ID or short string - Argument
body
: the full error dump, could be stack trace or error response from API
lodash
- a wrapper for all lodash methods
3 - AOM makes an instance of a flow
object available to the application-specific orchestration module. This flow
object provides:
a facade to a select subset of methods that are derived from the vasync module:
parallel(ArrayOfFunctions, callback)
: invoke N functions in parallel (and merge the results). Each function should expect arguments(flow, callback)
.waterfall(ArrayOfFunctions, callback)
: invoke N functions in series, propagating results between stages. The first function in the array should expect arguments(flow, callback)
. All subsequent functions should expect arguments(flow, dependency, callback)
, wheredependency
contains the data results of the previous functions within the chain.
access to the
apiOptions
,envVars
,reqParams
andreqQuery
properties.a method
mergeResultWithDerived(result, derived)
- merges a result object with a "derived" object; the derived object should contain any data that is derived/added by the orchestration logic to enhance API data. The derived data is placed in the contextProp namespace (='gc', the client-side global context)
Sample application-specific pseudo-code:
var apiUtils = require('shape-api-utils');
// the main function is called from the server
function main(flow, callback){
// we need to get data for events and artist, these calls can be made in
// 'parallel' since there is no dependency
flow.parallel([getUpcomingEvents, getArtistInfo], function(err, result){
// add derived data, for instance environment-specific settings
flow.mergeResultWithDerived(result, {
target_host: flow.apiOptions.params.host
});
callback(err, result);
});
}
// all functions that are called by the 'flow' methods have access to the
// 'apiOptions', 'reqParams' and 'reqQuery' objects (which were passed into 'main')
function getUpcomingEvents(flow, callback){
var uriVars = {
host: flow.apiOptions.params.host,
performerId: flow.reqParams.param,
start: flow.apiOptions.upcomingEvents.params.start,
rows: flow.apiOptions.upcomingEvents.params.rows
};
apiUtils.getApiData(flow.apiOptions.upcomingEvents, uriVars, callback);
}
// to get the artist info, we need to make two API calls: the URI for the second
// call needs to be constructed with data from the first call, hence there is a
// dependency between the two, which is handled by 'waterfall'.
function getArtistInfo(flow, callback){
flow.waterfall([callPerformerApi, callArtistApi], callback);
}
function callPerformerApi(flow, callback){
var uriVars = {
host: flow.apiOptions.params.host,
performerId: flow.reqParams.param
};
return apiUtils.getApiData(flow.apiOptions.performer, uriVars, callback);
}
// this method is called from within a waterfall and gets the result
// from the previous call through the 'dependency' argument
function callArtistApi(flow, dependency, callback){
var uriVars = {
extcatalogapi_host: flow.apiOptions.params.extcatalogapi_host,
artistNames: getPerformerName(dependency)
};
return apiUtils.getApiData(flow.apiOptions.artist, uriVars,
function(err, result){
// add derived data, and then return the result
flow.mergeResultWithDerived(result, {
performerName: artistNames,
artist_image: getArtistImagePath(result)
});
callback(err, result);
}
);
}
// a helper function that parses result data
function getPerformerName(result){
var performerName = "";
// custom logic to extract a performer name from the result
return performerName;
}
// a helper function that derives an image path
function getArtistImagePath(result){
var imgPath = "";
// custom logic to extract an image path from the result
return imgPath;
}
Sample SPA config for a QA environment:
General app.json
:
{
"bootstrapApiData": true,
"suppressJs": false,
"app_context": {
"appName": "performer",
"apiOptions": {
"performer": {
"uri": "http://{host}/shape/catalog/performers/v2/{performerId}",
"headers": {
"Accept": "application/json",
"Authorization": "Bearer JYf0azPrf1RAvhUhpGZudVU9bBEa"
}
},
"artist": {
"uri": "http://{extcatalogapi_host}/extcatalogapi/artists?names={artistNames}"
},
"upcomingEvents": {
"uri": "http://{host}/shape/search/catalog/events/v2/?performerId={performerId}&status=active&sort=dateLocal asc&start={start}&rows={rows}",
"headers": {
"Authorization": "Bearer JYf0azPrf1RAvhUhpGZudVU9bBEa"
},
"params": {
"start": 0,
"rows": 20
}
}
}
}
}
Environment-specific app_*.json
, for example app_development.json
:
{
"app_context": {
"apiOptions": {
"params": {
"host": "www.srwd33.com",
"domain": "srwd33.com",
"subdomain": "www",
"tld": "com",
"app_token": "JYf0azPrf1RAvhUhpGZudVU9bBEa",
"extcatalogapi_host": "srwd10evx001.srwd10.com:8080"
}
}
}
}