API Aggregation Using Azure API Management

Introduction

There are scenarios where the application needs to get information from multiple services to display a task/page because it needs to fetch the data from different back-end sources. The time taken for each service will impact the application's performance, and we never know how many more tasks we may call in future. What if there is an option where we only call one service and the response of it is a combination of all the required services? Using Azure API Management Service, we can achieve this.

Scenario

Consider a mobile client who needs to display work items from different projects, each project has different API end point. To display all the work items to the mobile client, we need to make two calls simultaneously and display the data.

The time response of each API at that time depends on the network availability, if it is having more API's in that page it takes more time and impacts application performance. If we use API Management we can get all the API responses on a single call. Although API Manager internally calls all the APIs required for a mobile page, the response time is less because the API resource is in the cloud with high bandwidth and gets the response fast. That improves the total time to load the page in mobile.
 
Things you should know

You need to have knowledge on rest services (API) and knowledge on how to integrate APIs to Azure API Management services and a basic idea of how to implement policies in API Manager. Please refer to Policies.
 
Step 1

First, we need to identify what APIs are required to aggregate and their input parameters. In my case, I had two APIs; one gives profile details and the other one fetches the user tasks of that user. 
  1. http://companywebapp.azurewebsites.net/api/Profile?id={accountId}  
  2. http://companyebapp.azurewebsites.net/api/Task?id={accountId}  
The common input parameter for both APIs is accountId.

Step 2

Create a new operation with name Dashboard as Get request with query parameter accountId and save. 



Step 3

Open the policy editor for Dashboard operation by clicking Inbound Processing-->Code view.

We need to create a variable with name accountId like below. We can use this variable to pass the value into the APIs.
  1. <set-variable name="accountId" value="@(context.Request.Url.Query["accountId"].Last())" />  
Step 4

Create a send-request policy for API1 and API2 which fetches profile details and profile tasks. The response-variable-name stores the response from the API. On set-url, we use the API to hit, which we want to fetch data from and stores the response to response-variable-name we declared. In set-method, we specify the HttpVerb based on the request we made. If we want to pass any values in the header we pass in set-header value.
  1. <send-request mode="new" response-variable-name="profiledetails" timeout="20" ignore-error="true">  
  2.             <set-url>@($"http://companywebapp.azurewebsites.net/api/Profile?id={(string)context.Variables["accountId"]}")</set-url>  
  3.             <set-method>GET</set-method>  
  4.             <set-header name="Content-Type" exists-action="override">  
  5.                 <value>application/x-www-form-urlencoded</value>  
  6.             </set-header>>  
  7.         </send-request>  
  1. <send-request mode="new" response-variable-name="taskdetails" timeout="20" ignore-error="true">  
  2.             <set-url>@($"http://companywebapp.azurewebsites.net/api/Task?id={(string)context.Variables["accountId"]}")</set-url>  
  3.             <set-method>GET</set-method>  
  4.             <set-header name="Content-Type" exists-action="override">  
  5.                 <value>application/x-www-form-urlencoded</value>  
  6.             </set-header>  
  7.         </send-request>  

Step 5

We need to collect the response from the two APIs and use return-response policy to send the data. In return-reponse policy we need to aggregate the responses in set-body policy. In the previous step we collected the response and stored in profiledetails and taskdetails.
This part is the final response, on set-body we are casting the response to JSON object and aggregating the two API response.
  1. <return-response>  
  2.             <set-status code="200" reason="OK" />  
  3.             <set-header name="Content-Type" exists-action="override">  
  4.                 <value>application/json</value>  
  5.             </set-header>  
  6.             <set-body>@(new JObject(new JProperty("profiledetails",((IResponse)context.Variables["profiledetails"]).Body.As<JObject>()),  
  7.                             new JProperty("taskdetails",((IResponse)context.Variables["taskdetails"]).Body.As<JObject>())  
  8.                             ).ToString())</set-body>  
  9.         </return-response>  
Summary

By following these simple steps, we can aggregate two API's responses and send a single collective response. The example is very simple but in a real-time scenario, you may encounter multiple APIs with a lot of input parameters.

Sometimes, we need to pass the Authorization token to every API request header or in a scenario based on one API response we need to call different APIs all we can achieve with API Manager.

Below is the code containing the complete policy with the Authorization Token.
  1. <policies>  
  2.     <inbound>  
  3.         <base />  
  4.         <set-variable name="accountId" value="@(context.Request.Url.Query["accountId"].Last())" />  
  5.         <set-variable name="token" value="@(context.Request.Headers.GetValueOrDefault("Authorization","scheme param"))" />  
  6.         <send-request mode="new" response-variable-name="profiledetails" timeout="20" ignore-error="true">  
  7.             <set-url>@($"http://companywebapp.azurewebsites.net/api/Profile?id={(string)context.Variables["accountId"]}")</set-url>  
  8.             <set-method>GET</set-method>  
  9.             <set-header name="Content-Type" exists-action="override">  
  10.                 <value>application/x-www-form-urlencoded</value>  
  11.             </set-header>  
  12.             <set-header name="Authorization" exists-action="override">  
  13.                 <value>@($"{(string)context.Variables["token"]}")</value>  
  14.             </set-header>  
  15.         </send-request>  
  16.         <send-request mode="new" response-variable-name="taskdetails" timeout="20" ignore-error="true">  
  17.             <set-url>@($"http://companywebapp.azurewebsites.net/api/Task?id={(string)context.Variables["accountId"]}")</set-url>  
  18.             <set-method>GET</set-method>  
  19.             <set-header name="Content-Type" exists-action="override">  
  20.                 <value>application/x-www-form-urlencoded</value>  
  21.             </set-header>  
  22.             <set-header name="Authorization" exists-action="override">  
  23.                 <value>@($"{(string)context.Variables["token"]}")</value>  
  24.             </set-header>  
  25.         </send-request>  
  26.         <return-response>  
  27.             <set-status code="200" reason="OK" />  
  28.             <set-header name="Content-Type" exists-action="override">  
  29.                 <value>application/json</value>  
  30.             </set-header>  
  31.             <set-body>@(new JObject(new JProperty("profiledetails",((IResponse)context.Variables["profiledetails"]).Body.As<JObject>()),  
  32.                             new JProperty("taskdetails",((IResponse)context.Variables["taskdetails"]).Body.As<JObject>())  
  33.                             ).ToString())</set-body>  
  34.         </return-response>  
  35.         <cache-lookup vary-by-developer="false" vary-by-developer-groups="false" allow-private-response-caching="true" downstream-caching-type="none">  
  36.             <vary-by-header>Accept</vary-by-header>  
  37.             <vary-by-header>Accept-Charset</vary-by-header>  
  38.         </cache-lookup>  
  39.     </inbound>  
  40.     <backend>  
  41.         <base />  
  42.     </backend>  
  43.     <outbound>  
  44.         <base />  
  45.         <cache-store duration="3600" />  
  46.     </outbound>  
  47.     <on-error>  
  48.         <base />  
  49.     </on-error>  
  50. </policies>