How To: Retrieve from CosmosDB using Azure API Management

Posted by

This post has been republished via RSS; it originally appeared at: Azure Developer Community Blog articles.

Overview

In this How To, I will show a simple mechanism for reading items from CosmosDB using Azure API Management (APIM). Azure Cosmos DB is a fully managed, serverless NoSQL database for high-performance applications of any size or scale. APIM is a API management service offering many features for manging APIs and exposing backend services.

 

Example Use Case

The example use case involves using CosmosDB to maintain a list of information and exposing the information using APIM as a public RSS feed. This will provide a user to illustrate APIM caching and transformation, and how APIM can call CosmosDB securely using managed identity.

 

Setup

The first step is to provision a CosmosDB and APIM service instance. In my resource group, I created the following services:

JeffreyChilberto_2-1664921228478.png

 

The first step is to get the URI for the new CosmosDB instance.

JeffreyChilberto_3-1664921393728.png

 

I also created several items that would be retrieved in the data explorer as illustrated below:

JeffreyChilberto_0-1664926340273.png

 

Over in the APIM, we will create a new HTTP API for our RSS Feed.

JeffreyChilberto_0-1664921046474.png

 

The Web Service URL is the CosmosDB URI copied earlier.

JeffreyChilberto_1-1664921144404.png

In the policy section, we will complete the API. Next though, let's setup the identity of the APIM.

 

Permissions

The next step is to enable the APIM to access CosmosDB. To do this, navigate to the Managed identities blade:

JeffreyChilberto_1-1663034928154.png

 

You will want the System assigned on:

JeffreyChilberto_2-1663035127321.png

 

To enable access to CosmosDB for APIM, we need to setup role-based access to the built-in role: Cosmos DB Built-in Data Reader. This article does a good job of explaining the setup. In the end, you will need a snippet to similar to the following:

 

 

$resourceGroupName = "<myResourceGroup>" $accountName = "<myCosmosAccount>" $readOnlyRoleDefinitionId = "<roleDefinitionId>" # as fetched above # For Service Principals make sure to use the Object ID as found in the Enterprise applications section of the Azure Active Directory portal blade. $principalId = "<aadPrincipalId>" New-AzCosmosDBSqlRoleAssignment -AccountName $accountName ` -ResourceGroupName $resourceGroupName ` -RoleDefinitionId $readOnlyRoleDefinitionId ` -Scope "/" ` -PrincipalId $principalId

 

 

 

 

The $resourceGroupName and $accountName can be taken from the CosmosDB overview:

JeffreyChilberto_4-1664923043782.png

 

The $principalId is taken from Managed identities Object (principal) ID shown previously. The $readOnlyRoleDefinitionId can be retrieved using the following command:

 

 

az cosmosdb sql role definition list --account-name $accountName --resource-group $resourceGroupName

 

 

 

 

With the RBAC in place, let's setup the CosmosDB request in the policy.

 

Policy

Now back on the API, select the operation and click the Policy code editor:

JeffreyChilberto_5-1664923290428.png

 

This will provide an xml editor. Insert the following xml in the inboud element.

 

 

 

<inbound> <base /> <set-variable name="requestDateString" value="@(DateTime.UtcNow.ToString("r"))" /> <authentication-managed-identity resource="https://cosmosrssfeed.documents.azure.com" output-token-variable-name="msi-access-token" ignore-error="false" /> <set-header name="Authorization" exists-action="override"> <value>@("type=aad&ver=1.0&sig=" + context.Variables["msi-access-token"])</value> </set-header> <set-header name="x-ms-date" exists-action="override"> <value>@(context.Variables.GetValueOrDefault<string>("requestDateString"))</value> </set-header> <set-header name="x-ms-version" exists-action="override"> <value>2018-12-31</value> </set-header> <set-header name="x-ms-documentdb-query-enablecrosspartition" exists-action="override"> <value>false</value> </set-header> <rewrite-uri template="/dbs/RssFeedDB/colls/RssFeed/docs/" copy-unmatched-params="false" /> </inbound>

 

 

 

 

This was derived from the List Documents Rest API and the Common Rest Request Headers. Take note of the authentication-managed-identity step in the policy to set teh msi-access-token variable used in the Authorization header.

 

An RSS feed is a perfect candidate for caching so I added the following to the inbound element:

 

 

 

<cache-lookup vary-by-developer="false" vary-by-developer-groups="false" downstream-caching-type="none"> <vary-by-header>Accept</vary-by-header> <vary-by-header>Accept-Charset</vary-by-header> <vary-by-header>Authorization</vary-by-header> </cache-lookup>

 

 

 

And, the following to the outbound policy.

 

 

<cache-store duration="20" />

 

 

 

 To alter the result to XML, I added the following to the outbound element:

 

 

<set-body template="liquid"> <rss version="2.0"> <channel> <title>My Information Feed</title> <link>https://apim-rssfeed-au-se.azure-api.net</link> <description>Super duper RSS Feed</description>{% for document in body.Documents %}<item><title>{{document.title}}</title><link>{{document.link}}</link><description>{{document.description}}</description></item>{% endfor %}</channel> </rss> </set-body> <set-header name="Content-Type" exists-action="override"> <value>application/xml</value> </set-header>

 

 

 

The body is transformed to XML using set-body and the liquid template. I originally tried using json-to-xml but found this too cumbersome. Also note the header is overridden to XML and be sure to put this before the base policy is called.

 

Test

Testing the API is nice and simple using the Test tab.

JeffreyChilberto_1-1664926413731.png

 

If the Azure gods are with you, you should not see an error and instead see a response transformed to XML.

JeffreyChilberto_0-1665001556411.png

 

 

Now, what if something goes wrong, or if you are not familiar with the Trace feature, read on.

 

Troubleshooting

The best way to troubleshoot is using Trace. This is located next to the send button.

JeffreyChilberto_8-1663036207787.png

 

For example, if you find the trace is not working you can determine if an entry was found in the cache as you will see a message similar to the following:

JeffreyChilberto_1-1664926764698.png

 

And subsequently, when there is a matching cache hit, the following message will be shown:

JeffreyChilberto_0-1664926758522.png

 

Trace is your friend :smile:

 

Summary

This How To illustrated a simple way to expose CosmosDB using API Management. We followed the best practice of using managed identities to setup trust between APIM and CosmosDB. The scenario also incorporated transforming the message from json to XML and illustrated a basic use of caching. Let me know if this saves you some time!

 

Cheers

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.