One of the coolest things about Azure Event Grid is that an event handler can be almost anything that accepts a HTTP POST. This got me thinking about how to use Event Grid in new ways with services on Azure that have yet to be leveraged.
In this post, I will demonstrate how to use Azure API Management (APIM) and Event Grid to unlock some interesting opportunities. If you are looking for ways to publish from APIM to Event Grid, please see this post: https://madeofstrings.com/2018/08/13/publishing-to-event-grid-from-azure-api-management/.
Several Azure services already integrate with Event Grid – Functions, Logic Apps, Event Hubs and Azure Automation all provide native support for subscribing to events. So then why would API Management be a valuable service to leverage?
A few of the solutions that would be enabled by API Management and Event Grid working together, include:
- Pushing events to internal APIs. Since an instance of API Management can be deployed within a VNET, events that are pushed from Grid could be routed to those services.
- Integration with legacy services. API Management can provide a facade to existing services that cannot be updated and know nothing about Event Grid. In addition, those services could be replaced, moved or even rewritten without the dependency of subscribing to Event Grid directly.
- Integration with other Azure services. From API Management, we can push messages to Cosmos DB and many other services on Azure.
- Pushing to external services. Events could be pushed from APIM to external services like Slack. The send one way request policy is just one example of how requests could be sent to other services.
The following figure shows the solution that we will walk through – events published by a custom application to Event Grid, handled by APIM and then displayed inside a Slack channel:
I’ll show you how to put this together in just a bit. First, it’ll be helpful to reflect on how events are delivered.
How Event Delivery Works
The delivery of events to an endpoint from Event Grid is well documented. All that is really required is that the endpoint is secure (HTTPS) and that it can echo back the validation code passed in. Both the header and the body of the message will contain the information necessary to determine what type of request is coming in.
There are two types of messages that Event Grid will send to a subscriber:
- Subscription validation – this will include the validation code that the subscriber must send back.
- Notification – this will contain contextual information from the event publisher about the event.
The best way to determine the event type (since it can vary by publishers) is to check the header value for Aeg-Event-Type.
API Management Event Handler
Policies in API Management, and the inbound statements specifically, is where we will do all the work. The complete solution on the API Management side looks like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<policies> | |
<inbound> | |
<base /> | |
<!– Check event type –> | |
<set-variable value="@(context.Request.Headers["Aeg-Event-Type"].Contains("SubscriptionValidation"))" name="isEventGridSubscriptionValidation" /> | |
<set-variable value="@(context.Request.Headers["Aeg-Event-Type"].Contains("Notification"))" name="isEventGridNotification" /> | |
<choose> | |
<when condition="@(context.Variables.GetValueOrDefault<bool>("isEventGridSubscriptionValidation"))"> | |
<return-response> | |
<set-status code="200" reason="OK" /> | |
<set-body>@{ | |
var events = context.Request.Body.As<string>(); | |
JArray a = JArray.Parse(events); | |
var eventGridData = a.First["data"]; | |
var validationCode = eventGridData["validationCode"]; | |
var jOutput = | |
new JObject( | |
new JProperty("validationResponse", validationCode) | |
); | |
return jOutput.ToString(); | |
}</set-body> | |
</return-response> | |
</when> | |
<when condition="@(context.Variables.GetValueOrDefault<bool>("isEventGridNotification"))"> | |
<send-one-way-request mode="new"> | |
<set-url>https://hooks.slack.com/services/{{slack-key}}</set-url> | |
<set-method>POST</set-method> | |
<set-body>@{ | |
var events = context.Request.Body.As<string>(); | |
JArray a = JArray.Parse(events); | |
var eventGridData = a.First["data"]; | |
var station = eventGridData["station"]; | |
var artist = eventGridData["artist"]; | |
var song = eventGridData["song"]; | |
return new JObject( | |
new JProperty("username", station), | |
new JProperty("icon_emoji", ":musical_note:"), | |
new JProperty("text", String.Format("Just added: {0}, {1}", | |
song, artist)) | |
).ToString(); | |
}</set-body> | |
</send-one-way-request> | |
<return-response> | |
<set-status code="200" reason="OK" /> | |
</return-response> | |
</when> | |
</choose> | |
</inbound> | |
<backend> | |
<base /> | |
</backend> | |
<outbound> | |
<base /> | |
</outbound> | |
<on-error> | |
<base /> | |
</on-error> | |
</policies> |
Let’s walk through some of the key parts. The first few statements retrieve the event type value from the header. These variables will be used to identify the type of incoming messages.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<set-variable value="@(context.Request.Headers["Aeg-Event-Type"].Contains("SubscriptionValidation"))" name="isEventGridSubscriptionValidation" /> | |
<set-variable value="@(context.Request.Headers["Aeg-Event-Type"].Contains("Notification"))" name="isEventGridNotification" /> |
The choose policy can then be applied to handle each type of message accordingly:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<choose> | |
<when condition="@(context.Variables.GetValueOrDefault<bool>("isEventGridSubscriptionValidation"))"> | |
<!– handle subscription validation –> | |
</when> | |
<when condition="@(context.Variables.GetValueOrDefault<bool>("isEventGridNotification"))"> | |
<!– handle notification message –> | |
</when> | |
</choose> |
The first condition is sending back the validation code within a property called validationResponse. Event Grid will send this message when the subscription is first created and also from time-to-time to ensure that it is still available:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<when condition="@(context.Variables.GetValueOrDefault<bool>("isEventGridSubscriptionValidation"))"> | |
<return-response> | |
<set-status code="200" reason="OK" /> | |
<set-body>@{ | |
var events = context.Request.Body.As<string>(); | |
JArray a = JArray.Parse(events); | |
var eventGridData = a.First["data"]; | |
var validationCode = eventGridData["validationCode"]; | |
var jOutput = | |
new JObject( | |
new JProperty("validationResponse", validationCode) | |
); | |
return jOutput.ToString(); | |
}</set-body> | |
</return-response> | |
</when> |
The second condition, supports the notification message from Event Grid. Remember, this is the message that is sent from the event publisher. The publisher could be one of the existing services on Azure – Blob Storage, Resource Groups and a few others, or it could even be from an application or service in the form a custom topic. Regardless, here is where things get interesting since this is where our options open up with how to handle the message. We could pass it along to another API, transform it into something else and so on.
For this example, a one way request is made to a Slack channel to demonstrate several things: how to call an external service and how to parse data from a custom event.
To keep things interesting, we are pretending that the event is published from a radio station and contains details about their current playlist. We’ll parse out some of the details from the event and sent it along to Slack:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<when condition="@(context.Variables.GetValueOrDefault<bool>("isEventGridNotification"))"> | |
<send-one-way-request mode="new"> | |
<set-url>https://hooks.slack.com/services/{{slack-key}}</set-url> | |
<set-method>POST</set-method> | |
<set-body>@{ | |
var events = context.Request.Body.As<string>(); | |
JArray a = JArray.Parse(events); | |
var eventGridData = a.First["data"]; | |
var station = eventGridData["station"]; | |
var artist = eventGridData["artist"]; | |
var song = eventGridData["song"]; | |
return new JObject( | |
new JProperty("username", station), | |
new JProperty("icon_emoji", ":musical_note:"), | |
new JProperty("text", String.Format("Just added: {0}, {1}", | |
song, artist)) | |
).ToString(); | |
}</set-body> | |
</send-one-way-request> | |
<return-response> | |
<set-status code="200" reason="OK" /> | |
</return-response> | |
</when> |
Creating an Event Subscription
Now it’s time to create the event subscription. We will need the following:
- A Event Grid Topic – there is a great quickstart for creating a topic here.
- The API Management endpoint – this is just the address and path of the API you want to register as the endpoint.
- The API Management subscription key – we will append this to the endpoint address to authorize the request.
The complete endpoint address should look something like this:
https://<apim-name>.azure-api.net/<api-path>?subscription-key=<apim-key>
The reason the APIM key is appended to the address is because Event Grid currently does not support the setting of header values in subscriptions. It only takes the address for now – luckily we can pass this in as a query string parameter.
Assuming you have a custom topic available, create the event subscription with the following command from the CLI:
az eventgrid topic event-subscription create --name <name-of-subscription> / --resource-group <resource-group-name> / --topic-name <event-grid-topic-name> / --endpoint <endpoint-address>
To confirm and list all the event subscriptions created for the topic, you can call:
az eventgrid topic event-subscription list --resource-group <resource-group-name> --topic <topic-name> --output table
Putting it All Together
All the pieces are in place and we can now send messages to Event Grid to test this end-to-end.
Using Postman, the header should include the SAS key for the Event Grid topic:
The body of the request will look like this:
When sent, we can expect a new message in the Slack channel:
Pretty cool, huh? Event Grid and API Management work really well together and open up a slew of new integration opportunities.
The policy used in this post can be found at the following GitHub repository: https://github.com/dbarkol/EventGrid-API-Management.
[…] David Barkol – Subscribing to Event Grid events with Azure API Management Azure Event Grid requires a subscription validation handshake, in order to ensure that the subscriber acknowledges it wants to receive the event. This blog explains how you can perform this handshake in Azure APIM, so you don’t need to modify the subscribing API itself. Read the complete article here. […]
[…] Subscribing to Event Grid events with API Management […]
[…] Management (APIM) and Event Grid integration has always been interesting to me. Luckily, registering a APIM endpoint for CloudEvents only involves a few […]
Thank you for the article, Very useful!