Mashup in Apigee Edge - Can I call API 1 then call API 2 recursively using the results of API 1? and transform the response data?

Hello everyone.

I have this case where:

1- I want to call API 1 that will return a lot of arrays and attributes.

2- I will do XSL transformation to get only ids and put them into an array.

3- for each id I want to call a second API to enrich the message with full information about the object.

4- Transform each object to get certain attributes.

5- return an array of objects that has been enriched and transformed.

I was able to do the first two points and here is a sample of the response:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <procedures>
	<procedureId>1</procedureId>
        <procedureId>2</procedureId>
        <procedureId>3</procedureId>
    </procedures>
</root>

For API 2 here is a sample response:

<?xml version="1.0" encoding="UTF-8"?>
<root>
    <procedureDetails>
        <procedureId>1</procedureId>
        <Detail>
            <DetailType>Last change</DetailType>
            <value>2018-04-01T06:43:44Z</value>
        </Detail>
        <Detail>
            <DetailType>Service status </DetailType>
            <value>True</value>
        </Detail>
    </procedureDetails>
</root>

Here is a pseudo code of the API

Call API 1
Transform message to get only ids.

foreach(id in ids)

Call API 2
Transform message to get certain attributes only
end foreach

return an array list of objects.

So far I have created two Proxy Endpoints: API 1 and API 2. each has a transformation policy. I want to combine the two to get an array of objects with their information. How can I call a Proxy Endpoints recursively? If I can't. Can I call two saparate proxies recursively?

Solved Solved
1 5 917
1 ACCEPTED SOLUTION

As Siddharth explained, Apigee Edge is not an ESB; it does not provide broad support for integration and elaboration. But if your needs are as simple as you describe, Apigee Edge will work just fine for your purposes.

There are various ways to do it. One way to accomplish what you want is to:

  1. Build an API Proxy
  2. set API-1 as the target
  3. In the "response flow", set an XSL policy that loops through the procedureId values, and gets an XML document from API-2 for each one. Then this XSL concatenates a transformation of each one of those retrieved document, into one consolidated XML response.

The XSL "apply-templates" element effectively acts as the loop that you mentioned. It can loop through each procedureId returned from the original endpoint.

Many people are unaware that an XSL stylesheet, including one running within Apigee Edge, can retrieve an XML document from an external endpoint. (see also, this related post.) . This is done with a statement like this:

    <xsl:variable name="xdoc" select="document($location)"/>

...where location refers to an XSL variable pointing to an https endpoint. The full sheet might look like this:

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <!-- this is a parameter set in the XSL Policy config -->
  <xsl:param name="base_location" select="''"/>

  <xsl:template match="/">
    <!-- Elaborate the procedures -->
    <root>
      <xsl:apply-templates select="root/procedures/procedureId" />
    </root>
  </xsl:template>

  <xsl:template match='procedureId'>
    <xsl:variable name="location" select="concat($base_location, normalize-space(./text()))"/>
    <!-- the following retrieves the remote document into a variable
         available to the stylesheet -->
    <xsl:variable name="xdoc" select="document($location)"/>
    <xsl:apply-templates select="$xdoc/root/procedureDetails"/>
  </xsl:template>

  <xsl:template match='procedureDetails'>...</xsl:template>
</xsl:stylesheet>

If API-1 returns something like this:

<root>
   <procedures>
      <procedureId>5800</procedureId>
      <procedureId>2842</procedureId>
   </procedures>
</root>

...and if API-2 returns something like this:

<root>
  <procedureDetails>
    <procedureId>5800</procedureId>
    <Detail>
      <DetailType>Last change</DetailType>
      <value>2018-12-10T19:11Z</value>
    </Detail>
    <Detail>
      <DetailType>Service status</DetailType>
      <value>true</value>
    </Detail>
  </procedureDetails>
</root>

..then that stylesheet will produce something like this:

<root>
   <procedure>
      <id>5800</id>
      <Last_change>2018-12-09T19:11Z</Last_change>
      <Service_status>true</Service_status>
   </procedure>

   <procedure>
      <id>2842</id>
      <Last_change>2018-12-08T17:18Z</Last_change>
      <Service_status>true</Service_status>
   </procedure>
</root>

To get this to work, you would need to specify a parameter to the XSL policy, like this:

<XSL name='XSL-ElaborateAndMashup'>
  <Source>response</Source>
  <OutputVariable>response.content</OutputVariable>
  <ResourceURL>xsl://elaborate-and-mashup.xsl</ResourceURL>
  <Parameters ignoreUnresolvedVariables='true'>
    <Parameter name='base_location' ref='base_location'/>
  </Parameters>
</XSL>

...and the context variable 'base_location', would need to hold something like "https://base.com/foo/" .

Attached is a working API Proxy that shows all the various policies. apiproxy-elaboration-mashup.zip

Another way is to use JavaScript to do the retrieving. In that case you will probably want to mashup the retrieved contents in the JS callout. You can do that with e4X.

A third way is to retrieve all the docs with JS, then mash them up in XSL. I think I like that way the best. Because you can retrieve documents in JS asynchronously, this approach might perform better at scale than the simpler approach of using the XSL to retrieve documents serially.

You could also use a Hosted Target implementation to parse the initial XML and then invoke N other endpoints. But that seems like overkill for a simple http callout.

View solution in original post

5 REPLIES 5

Hi @Saud Alsaeed ,

  • do you know how many IDs you will be getting in Step2? or is it dynamic?
    • if you are expecting more than 10 ids, then maybe Apigee Proxy wont be a good idea, it should be handled in an ESB or something similar.
  • why are you having two API proxies?
    • if you go with one proxy maybe we can use JavaScript policy to make multiple API calls.
  • Proxy endpoints are REST services so we can call them using Service Callout Policy(HTTP/ProxyChaining) or Javascript as well.

Thank you. I will keep this in mind.

As Siddharth explained, Apigee Edge is not an ESB; it does not provide broad support for integration and elaboration. But if your needs are as simple as you describe, Apigee Edge will work just fine for your purposes.

There are various ways to do it. One way to accomplish what you want is to:

  1. Build an API Proxy
  2. set API-1 as the target
  3. In the "response flow", set an XSL policy that loops through the procedureId values, and gets an XML document from API-2 for each one. Then this XSL concatenates a transformation of each one of those retrieved document, into one consolidated XML response.

The XSL "apply-templates" element effectively acts as the loop that you mentioned. It can loop through each procedureId returned from the original endpoint.

Many people are unaware that an XSL stylesheet, including one running within Apigee Edge, can retrieve an XML document from an external endpoint. (see also, this related post.) . This is done with a statement like this:

    <xsl:variable name="xdoc" select="document($location)"/>

...where location refers to an XSL variable pointing to an https endpoint. The full sheet might look like this:

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <!-- this is a parameter set in the XSL Policy config -->
  <xsl:param name="base_location" select="''"/>

  <xsl:template match="/">
    <!-- Elaborate the procedures -->
    <root>
      <xsl:apply-templates select="root/procedures/procedureId" />
    </root>
  </xsl:template>

  <xsl:template match='procedureId'>
    <xsl:variable name="location" select="concat($base_location, normalize-space(./text()))"/>
    <!-- the following retrieves the remote document into a variable
         available to the stylesheet -->
    <xsl:variable name="xdoc" select="document($location)"/>
    <xsl:apply-templates select="$xdoc/root/procedureDetails"/>
  </xsl:template>

  <xsl:template match='procedureDetails'>...</xsl:template>
</xsl:stylesheet>

If API-1 returns something like this:

<root>
   <procedures>
      <procedureId>5800</procedureId>
      <procedureId>2842</procedureId>
   </procedures>
</root>

...and if API-2 returns something like this:

<root>
  <procedureDetails>
    <procedureId>5800</procedureId>
    <Detail>
      <DetailType>Last change</DetailType>
      <value>2018-12-10T19:11Z</value>
    </Detail>
    <Detail>
      <DetailType>Service status</DetailType>
      <value>true</value>
    </Detail>
  </procedureDetails>
</root>

..then that stylesheet will produce something like this:

<root>
   <procedure>
      <id>5800</id>
      <Last_change>2018-12-09T19:11Z</Last_change>
      <Service_status>true</Service_status>
   </procedure>

   <procedure>
      <id>2842</id>
      <Last_change>2018-12-08T17:18Z</Last_change>
      <Service_status>true</Service_status>
   </procedure>
</root>

To get this to work, you would need to specify a parameter to the XSL policy, like this:

<XSL name='XSL-ElaborateAndMashup'>
  <Source>response</Source>
  <OutputVariable>response.content</OutputVariable>
  <ResourceURL>xsl://elaborate-and-mashup.xsl</ResourceURL>
  <Parameters ignoreUnresolvedVariables='true'>
    <Parameter name='base_location' ref='base_location'/>
  </Parameters>
</XSL>

...and the context variable 'base_location', would need to hold something like "https://base.com/foo/" .

Attached is a working API Proxy that shows all the various policies. apiproxy-elaboration-mashup.zip

Another way is to use JavaScript to do the retrieving. In that case you will probably want to mashup the retrieved contents in the JS callout. You can do that with e4X.

A third way is to retrieve all the docs with JS, then mash them up in XSL. I think I like that way the best. Because you can retrieve documents in JS asynchronously, this approach might perform better at scale than the simpler approach of using the XSL to retrieve documents serially.

You could also use a Hosted Target implementation to parse the initial XML and then invoke N other endpoints. But that seems like overkill for a simple http callout.

Here's the version that uses JS to mash up the responses.

apiproxy-elaboration-mashup-js.zip

Thank you very much. I have learned so much form the above. From syntax to good practices of transformation, and much more. I will continue experimenting until I find what suits my needs.I appreciate the quick responses. Your response is more than enough to do what I need.