This topic is based on the policy composition sample proxy available in the API Platform samples on Github. The application in this topic replicates the same functionality as Building a composite service using JavaScript, using a different approach: policy composition.
Policies are pre-built, atomic units of functionality. These units can be assembled into sequences of interlocking behaviors that generate self-contained applications.
The primary policy type that enables composite service functionality is the ServiceCallout policy. A ServiceCallout policy acts as an HTTP client to an API or service. To be completely functional, however, a ServiceCallout relies on other policies to enable meaningful interaction with a remote API or service. Other policies are used to generate the request message that is sent by the ServiceCallout and to parse the content in the response message from the remote API or service.
The composite service in this example consumes two public APIs:
- The Google Geocoding API
- The Google Elevation API
From the requesting client app's point of view, the composite service implemented by this policy composition returns a JSON response. The JSON response includes the geocoded location for the center of the postal code provided by the app end user, combined with the elevation at that geocoded location.
The composite service created here also exposes its own API. The API takes two query parameters:
- postalcode: A postal code valid in the country specified
- country: A two-letter country code
Structure of a policy composition
The policy composition for performing a ServiceCallout is typically the following:
- AssignMessage: Create the message object that will be forwarded by the service callout policy in the next processing step.
- ServiceCallout: Define the HTTP settings for the connection to the remote HTTP service or API.
- ExtractVariables: Parse the response message from the remote HTTP service or API and populate variables. The variables are then available to be used, for example, to populate another message, which might be returned to the requesting app.
A ServiceCallout policy composition is only one logical unit within the larger API proxy flow. The ServiceCallout composition populates data from one API or service that is consumed and recombined by other policies or pieces of code that implement the API proxy behavior as a whole.
This example uses a series of policies in order to implement a complete composite service:
- AssignMessage: Generates the request for the geocoding API
- ServiceCallout: Sends the request to the geocoding API
- ExtractVariables: Parses the geocoding response and extracts the latitude and longitude
- AssignMessage: Sets the parameters for the elevation service
- ExtractVariables: Parses the elevation response
- Javascript: Generates the final response JSON, combining the variables set by the previous policies
Create policies
Create request with AssignMessage
An AssignMessage policy is used to generate the request message that the ServiceCallout policy will send to the geocoding service. Since the request is an HTTP GET, it only requires query parameters. The policy below sets three query parameters, whose values are extracted from the query parameters on the request message from the client app. The first two query parameters, postal code and country, will be used to create the response message, so this policy is also configured to populate a custom variable to hold each value.
<AssignMessage name="GenerateGeocodingRequest">
<AssignTo createNew="true" type="request">GeocodingRequest</AssignTo>
<Set>
<QueryParams>
<QueryParam name="address">{request.queryparam.postalcode}</QueryParam>
<QueryParam name="region">{request.queryparam.country}</QueryParam>
<QueryParam name="sensor">false</QueryParam>
</QueryParams>
<Verb>GET</Verb>
</Set>
<!-- Set variables for use in the final response -->
<AssignVariable>
<Name>PostalCode</Name>
<Ref>request.queryparam.postalcode</Ref>
</AssignVariable>
<AssignVariable>
<Name>Country</Name>
<Ref>request.queryparam.country</Ref>
</AssignVariable>
</AssignMessage>
Send the request with ServiceCallout
The ServiceCallout policy below is configured to send the request (GeocodingRequest) created by the previous policy to the geocoding service, and to save the result as GeocodingResponse.
<ServiceCallout name="ExecuteGeocodingRequest">
<Request variable="GeocodingRequest"/>
<Response>GeocodingResponse</Response>
<HTTPTargetConnection>
<URL>http://maps.googleapis.com/maps/api/geocode/json</URL>
</HTTPTargetConnection>
</ServiceCallout>
Parse the response with ExtractVariables
A ServiceCallout policy is usually followed by an ExtractVariables policy. ExtractVariables provides a simple mechanism for parsing content from the response message obtained by a ServiceCallout. ExtractVariables can be used to parse JSON or XML, or it can be used to extract content from URI paths, HTTP headers, query parameters, and form parameters.
The key components of an ExtractVariable policy are:
- Source: In this case, the source of the variables is the
GeocodingResponseobject created by the - ServiceCallout policy in the previous step.
- VariablePrefix: When a custom variable is defined, a prefix is required to create a namespace for the variables. In the example, the variable prefix is
geocodresponse, but any prefix can be used, except the reserved names defined by the API Platform's Predefined Variables. - The message content containing the target values: In this case, a JSONPayload is parsed to obtain variable values. Other options include: URI path, HTTP headers, query parameters, form parameters, and XML payloads. For instructions on configuring patterns to extract variables, see Extract message content using extract variable.
- Variable name: The name used to identify the specific variable, in this case,
latitudeandlongitudeOther policies or code can retrieve the values set by referring to these variables.
<ExtractVariables name="ParseGeocodingResponse">
<Source>GeocodingResponse</Source>
<VariablePrefix>geocoderesponse</VariablePrefix>
<JSONPayload>
<Variable name="latitude">
<JSONPath>$.results[0].geometry.location.lat</JSONPath>
</Variable>
<Variable name="longitude">
<JSONPath>$.results[0].geometry.location.lng</JSONPath>
</Variable>
</JSONPayload>
</ExtractVariables>
The result of this policy configuration is two custom variables populated with values extracted from the JSONPayload of the geocoding response:
geocoderesponse.latitudegeocoderesponse.longitude
These variables are available for use by other policies or code (JavaScript, Python, or Java) executing in the API proxy.
This completes the ServiceCallout policy 'composition'. Three policies were used to execute a request/response transaction with a remote API. Those policies are AssignMessage, ServiceCallout, and ExtractVariable. The outcome of the ServiceCallout composition is data that is available to be sued or enriching request or response messages, for performing custom analytics to gain visbility into business-specific API usage, for building traditional mashups, and so on.
Generate the request with AssignMessage
A request message is populated with the variables extracted from the geocoding API. This is done, as above, using an AssignMessage policy. The request message generated in this step does not require a ServiceCallout, as the request generated for the main request pipeline, and so will simply be forwarded by the ProxyEndpoint to the TargetEndpoint, following the RouteRule configured for this API proxy. The TargetEndpoint manages the connection with the remote API. (Recall that the URL for the elevation API is defined in the HTTPConnection for the TargetEndpoint.)
<AssignMessage name="AssignElevationParameters">
<Remove>
<QueryParams>
<QueryParam name="country"/>
<QueryParam name="postalcode"/>
</QueryParams>
</Remove>
<Set>
<QueryParams>
<QueryParam name="locations">{geocoderesponse.latitude},{geocoderesponse.longitude}</QueryParam>
<QueryParam name="sensor">false</QueryParam>
</QueryParams>
</Set>
</AssignMessage>
Parse the XML response with ExtractVariables
In this example, the response from the Elevation API is returned as XML. Once again, the ExtractVariables policy is used to parse the response message and populate a custom variable that makes the data available to other components in the API proxy. In the example below, XPath is used to parse the XML response and to obtain the value of the elevation returned by the Elevation API.
<ExtractVariables name="ParseElevationResponse">
<VariablePrefix>elevationresponse</VariablePrefix>
<XMLPayload>
<Variable name="elevation" type="float">
<XPath>/ElevationResponse/result[1]/elevation/text()</XPath>
</Variable>
</XMLPayload>
</ExtractVariables>
Generate a composite response with JavaScript
In this example, JavaScript is used to construct a response for requesting client that contains the data populated by the previous policy executions. The JavaScript below is written to the Apigee JavaScript Object Model, and it is stored in the the /resources/jsc directory in the API proxy configuration.
// Get the variables set by ParseElevationResponse
var elevationMeters = context.getVariable('elevationresponse.elevation');
var elevationFeet = elevationMeters * 3.2808399;
// Re-initialize the response. This variable currently holds the response from the
// elevation service, which is XML
response.content = '';
response.headers['Content-Type'] = 'application/json';
// Create and populate a new JSON object for the response
var r = response.content.asJSON;
r.country = context.getVariable('Country');
r.postalcode = context.getVariable('PostalCode');
var el = new Object();
el.meters = elevationMeters;
el.feet = elevationFeet;
r.elevation = el;
var loc = new Object();
loc.latitude = context.getVariable('geocoderesponse.latitude');
loc.longitude = context.getVariable('geocoderesponse.longitude');
r.location = loc;
Execute the JavaScript to generate a response
To execute the JavaScript as a processing step in the ProxyEndpoint flow, it must be attached to the flow using a JavaScript policy that references the JavaScript resource by name. Note the name provided as the ResourceURL for the JavaScript must be exactly the same as the file saved under /resources/jsc. If the name is not the same, then the API Platform will throw an InternalClassification error.
jsc://GenerateResponse.js
Attach policies to the ProxyEndpoint Request and Response flows
The ProxyEndpoint flow configuration for this composite service is:
≪Flows≫
≪Flow Name="default"≫
≪Request≫
≪!-- Generate the Request to the Geocoding Service --≫
≪Step≫≪Name≫Generategeocodingrequest≪/Name≫≪/Step≫
≪!-- Send Request to the Geocoding Service --≫
≪Step≫≪Name≫Executegeocodingrequest≪/Name≫≪/Step≫
≪!-- Parse the Json Response and Set Variables --≫
≪Step≫≪Name≫Parsegeocodingresponse≪/Name≫≪/Step≫
≪!-- Generate the Request for the Elevation Service --≫
≪Step≫≪Name≫Assignelevationparameters≪/Name≫≪/Step≫
≪/Request≫
≪Response≫
≪!-- Parse the Xml Response from the Elevation Service --≫
≪Step≫≪Name≫Parseelevationresponse≪/Name≫≪/Step≫
≪!-- Generate the Json Response for the Requesting Client App --≫
≪Step≫≪Name≫Generateresponse≪/Name≫≪/Step≫
≪/Response≫
≪/Flow≫
≪/Flows≫
Import and deploy
Deploy the API proxy as usual, using the deploy tool, substituting valid username, password, and organization for an account on Apigee Enterprise:
$ python tools/deploy.py -n weatherapi -u myname:mypass -o myorg -e test -p / -d simpleProxy
Test
The composite service is invoked as follows:
$ curl "http://{myorg}-test.apigee.net/altitude?country=us&postalcode=08008"
Example response:
{"country":"us","postalcode":"08008",
"elevation":{"meters":0.5045232,"feet":1.6552599030345978},
"location":{"latitude":39.75007129999999,"longitude":-74.1357407}}
Get help
Post questions to the Apigee Developer Forum.