0.1.0 • Published 10 years ago
shape-api-utils v0.1.0
##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
mainmethod in an application-specific orchestration module, or returns an error to thecallbackmethod if no application-specific orchestration module can be found. - Argument
optsis cloned and then passed into themainmethod. It is expected to contain:appOrchModule: a resolvable path to an application-specific orchestration moduleapiOptions: a json configuration object containingrequestoptions 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 (seemergeResultWithDerivedbelow)
- Argument
callbackis expected to be a callback function which will be called when the orchestration finishes. - Argument
objsis 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 theuriproperty 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
requestmodule 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 auriproperty which may have variable placeholders enclosed in curly braces ({myVar}), and may contain optionalheadersthat 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), wheredependencycontains the data results of the previous functions within the chain.
access to the
apiOptions,envVars,reqParamsandreqQueryproperties.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"
}
}
}
}0.1.0
10 years ago