Developing Middleware With Microsoft Azure Service Bus And Functions

Introduction

 
A middleware is a software service that glues together multiple services. In today's business needs, multiple software services and technologies need to work together and communicate with each other. It is not necessary that these distributed software services are compatible with each other and will be able to communicate.
 
Example Business Case
 
We have to develop a software service in which we have geo-coordinates of a location and we need to get weather information of the city based on those coordinates. We have a system X that needs to communicate with another system Y. These are distributed systems. System X has information about geo coordinates and system Y will store weather information of the city based on those coordinates.
 

Solution

 
We will develop a middleware between system X and system Y.
 

Middleware Architecture

 
Developing Middleware With Microsoft Azure Service Bus And Functions
  1. System X will send geo coordinates to the receiver service (Http Triggered Azure Function) of the middleware in JSON format.
  2. Receiver service will call reverse geocoder API and will extract city name from the response and finally sends the city name to the Service Bus queue.
  3. Sender service will receive city name from service and call weather API and send weather data to System Y (Service Bus Queue Triggered Function).
  4. System Y will receive weather information from the sender service and store it (For sake of simplicity, we will log the information at Sender Service).
Prerequisites
  1. Microsoft Azure Subscription.
  2. Deployed Service Bus resource on Microsoft Azure Portal.
  3. Postman for testing
  4. Visual Studio 2019
  5. .NET Core 3.1
APIs Used
  1. Open Weather Map
  2. Big Data Cloud Reverse Geocode

APIs Formats

 
Request From System X
  1. {  
  2.     "latitude":51.5074,  
  3.     "longitude":0.1278  
  4. }  
Reverse Geocode API Response
  1. {  
  2.   "latitude": 37.42158889770508,  
  3.   "longitude": -122.08370208740234,  
  4.   "plusCode""849VCWC8+JG",  
  5.   "localityLanguageRequested""en",  
  6.   "continent""North America",  
  7.   "continentCode""NA",  
  8.   "countryName""United States of America",  
  9.   "countryCode""US",  
  10.   "principalSubdivision""California",  
  11.   "principalSubdivisionCode""US-CA",  
  12.   "city""Mountain View",  
  13.   "locality""Googleplex",  
  14.   "postcode""94043",  
  15.   "localityInfo": {  
  16.     "administrative": [  
  17.       {  
  18.         "order": 1,  
  19.         "adminLevel": 2,  
  20.         "name""United States of America",  
  21.         "description""country in North America",  
  22.         "isoName""United States of America (the)",  
  23.         "isoCode""US",  
  24.         "wikidataId""Q30",  
  25.         "geonameId": 6252001  
  26.       },  
  27.       {  
  28.         "order": 4,  
  29.         "adminLevel": 4,  
  30.         "name""California",  
  31.         "description""state of the United States of America",  
  32.         "isoName""California",  
  33.         "isoCode""US-CA",  
  34.         "wikidataId""Q99",  
  35.         "geonameId": 5332921  
  36.       },  
  37.       {  
  38.         "order": 6,  
  39.         "adminLevel": 6,  
  40.         "name""Santa Clara County",  
  41.         "description""county in California, United States",  
  42.         "wikidataId""Q110739",  
  43.         "geonameId": 5393021  
  44.       },  
  45.       {  
  46.         "order": 7,  
  47.         "adminLevel": 8,  
  48.         "name""Mountain View",  
  49.         "description""city"  
  50.       }  
  51.     ],  
  52.     "informative": [  
  53.       {  
  54.         "order": 0,  
  55.         "name""North America",  
  56.         "description""continent on the Earth's northwestern quadrant",  
  57.         "isoCode""NA",  
  58.         "wikidataId""Q49",  
  59.         "geonameId": 6255149  
  60.       },  
  61.       {  
  62.         "order": 2,  
  63.         "name""contiguous United States",  
  64.         "description""48 states of the United States apart from Alaska and Hawaii",  
  65.         "wikidataId""Q578170"  
  66.       },  
  67.       {  
  68.         "order": 3,  
  69.         "name""America/Los_Angeles Timezone",  
  70.         "description""timezone"  
  71.       },  
  72.       {  
  73.         "order": 5,  
  74.         "name""Pacific Coast Ranges",  
  75.         "description""A series of mountain ranges along the Pacific coast of North America",  
  76.         "wikidataId""Q660304"  
  77.       },  
  78.       {  
  79.         "order": 8,  
  80.         "name""94043",  
  81.         "description""postal code"  
  82.       },  
  83.       {  
  84.         "order": 9,  
  85.         "name""Googleplex",  
  86.         "description""building complex in California, United States",  
  87.         "wikidataId""Q694178",  
  88.         "geonameId": 6301403  
  89.       }  
  90.     ]  
  91.   }  
  92. }  
Open Weather Map API Response
  1. {  
  2.     "coord": {  
  3.         "lon": -0.13,  
  4.         "lat": 51.51  
  5.     },  
  6.     "weather": [  
  7.         {  
  8.             "id": 501,  
  9.             "main""Rain",  
  10.             "description""moderate rain",  
  11.             "icon""10d"  
  12.         }  
  13.     ],  
  14.     "base""stations",  
  15.     "main": {  
  16.         "temp": 287.47,  
  17.         "feels_like": 287.02,  
  18.         "temp_min": 287.04,  
  19.         "temp_max": 287.59,  
  20.         "pressure": 991,  
  21.         "humidity": 100  
  22.     },  
  23.     "visibility": 10000,  
  24.     "wind": {  
  25.         "speed": 2.6,  
  26.         "deg": 80  
  27.     },  
  28.     "rain": {  
  29.         "1h": 1.15  
  30.     },  
  31.     "clouds": {  
  32.         "all": 100  
  33.     },  
  34.     "dt": 1603273771,  
  35.     "sys": {  
  36.         "type": 1,  
  37.         "id": 1414,  
  38.         "country""GB",  
  39.         "sunrise": 1603262110,  
  40.         "sunset": 1603299281  
  41.     },  
  42.     "timezone": 3600,  
  43.     "id": 2643743,  
  44.     "name""London",  
  45.     "cod": 200  
  46. }   

Receiver Service

 
Receiver Service is implemented as Azure Function with Http trigger. We also have to create classes for request from System X and for a response from Geocode API. We need classes for JSON requests and responses, so we can deserialize them into C# objects and access information in them. It is also helpful for mapping data to another object.
 
GeoRequest Class
  1. public class GeoRequest  
  2. {  
  3.     public float latitude { getset; }  
  4.     public float longitude { getset; }  
  5. }  
GeoResponse Class
  1. public class GeoResponse  
  2. {  
  3.     public float latitude { getset; }  
  4.     public float longitude { getset; }  
  5.     public string plusCode { getset; }  
  6.     public string localityLanguageRequested { getset; }  
  7.     public string continent { getset; }  
  8.     public string continentCode { getset; }  
  9.     public string countryName { getset; }  
  10.     public string countryCode { getset; }  
  11.     public string principalSubdivision { getset; }  
  12.     public string principalSubdivisionCode { getset; }  
  13.     public string city { getset; }  
  14.     public string locality { getset; }  
  15.     public string postcode { getset; }  
  16.     public Localityinfo localityInfo { getset; }  
  17. }  
  18.   
  19. public class Localityinfo  
  20. {  
  21.     public Administrative[] administrative { getset; }  
  22.     public Informative[] informative { getset; }  
  23. }  
  24.   
  25. public class Administrative  
  26. {  
  27.     public int order { getset; }  
  28.     public int adminLevel { getset; }  
  29.     public string name { getset; }  
  30.     public string description { getset; }  
  31.     public string isoName { getset; }  
  32.     public string isoCode { getset; }  
  33.     public string wikidataId { getset; }  
  34.     public int geonameId { getset; }  
  35. }  
  36.   
  37. public class Informative  
  38. {  
  39.     public int order { getset; }  
  40.     public string name { getset; }  
  41.     public string description { getset; }  
  42.     public string wikidataId { getset; }  
  43.     public int geonameId { getset; }  
  44. }  

Receiver Function

  1. using System;  
  2. using System.IO;  
  3. using System.Threading.Tasks;  
  4. using Microsoft.AspNetCore.Mvc;  
  5. using Microsoft.Azure.WebJobs;  
  6. using Microsoft.Azure.WebJobs.Extensions.Http;  
  7. using Microsoft.AspNetCore.Http;  
  8. using Microsoft.Extensions.Logging;  
  9. using Newtonsoft.Json;  
  10. using Microsoft.Azure.ServiceBus;  
  11. using System.Text;  
  12.   
  13. namespace Receiver  
  14. {  
  15.     public static class ReceiverFunction  
  16.     {  
  17.         // Initialize Variables  
  18.         static string connection = "Service_Bus_Connection_String";  
  19.         static string queue = "Queue_Name";  
  20.   
  21.         [FunctionName("Receiver")]  
  22.         public static async Task<IActionResult> Run(  
  23.             [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,  
  24.             ILogger log)  
  25.         {  
  26.             // Read Request Body Stream to String  
  27.             string requestString = new StreamReader(req.Body).ReadToEnd();  
  28.             // Deserialize Request into GeoRequest object  
  29.             GeoRequest geoRequest = JsonConvert.DeserializeObject<GeoRequest>(requestString);  
  30.   
  31.             // Send Request to Reverse Geocode API  
  32.             HttpClient httpClient = new HttpClient();  
  33.             HttpRequestMessage requestMessage = new HttpRequestMessage  
  34.             {  
  35.                 Method = HttpMethod.Get,  
  36.                 // Using string intepolation in URL to add latitude and longitude  
  37.                 RequestUri = new Uri($"https://api.bigdatacloud.net/data/reverse-geocode-client?latitude={geoRequest.latitude}&longitude={geoRequest.longitude}&localityLanguage=en")  
  38.             };  
  39.   
  40.             // Sending request and receiving response message  
  41.             HttpResponseMessage response = await httpClient.SendAsync(requestMessage);  
  42.   
  43.             // Reading response body as string  
  44.             string responseString = await response.Content.ReadAsStringAsync();  
  45.   
  46.             // Deserialize Request into GeoResponse object  
  47.             GeoResponse geoResponse = JsonConvert.DeserializeObject<GeoResponse>(responseString);  
  48.   
  49.             // Sending city name to Service Bus Queue  
  50.             IQueueClient queueClient = new QueueClient(connection, queue);  
  51.   
  52.             Message message = new Message(Encoding.UTF8.GetBytes(geoResponse.city));  
  53.   
  54.             await queueClient.SendAsync(message);  
  55.   
  56.             return new OkObjectResult(responseString);  
  57.         }  
  58.     }  
  59. }   

Sender Service

 
Sender Service is also implemented as Azure Function with Service Bus Queue trigger. As soon as there's a message in Service Bus Queue, the Sender Service will be executed.
 
WeatherResponse
  1. public class WeatherResponse  
  2.     {  
  3.         public Coord coord { getset; }  
  4.         public Weather[] weather { getset; }  
  5.         public string _base { getset; }  
  6.         public Main main { getset; }  
  7.         public int visibility { getset; }  
  8.         public Wind wind { getset; }  
  9.         public Rain rain { getset; }  
  10.         public Clouds clouds { getset; }  
  11.         public int dt { getset; }  
  12.         public Sys sys { getset; }  
  13.         public int timezone { getset; }  
  14.         public int id { getset; }  
  15.         public string name { getset; }  
  16.         public int cod { getset; }  
  17.     }  
  18.   
  19.     public class Coord  
  20.     {  
  21.         public float lon { getset; }  
  22.         public float lat { getset; }  
  23.     }  
  24.   
  25.     public class Main  
  26.     {  
  27.         public float temp { getset; }  
  28.         public float feels_like { getset; }  
  29.         public float temp_min { getset; }  
  30.         public float temp_max { getset; }  
  31.         public int pressure { getset; }  
  32.         public int humidity { getset; }  
  33.     }  
  34.   
  35.     public class Wind  
  36.     {  
  37.         public float speed { getset; }  
  38.         public int deg { getset; }  
  39.     }  
  40.   
  41.     public class Rain  
  42.     {  
  43.         public float _1h { getset; }  
  44.     }  
  45.   
  46.     public class Clouds  
  47.     {  
  48.         public int all { getset; }  
  49.     }  
  50.   
  51.     public class Sys  
  52.     {  
  53.         public int type { getset; }  
  54.         public int id { getset; }  
  55.         public string country { getset; }  
  56.         public int sunrise { getset; }  
  57.         public int sunset { getset; }  
  58.     }  
  59.   
  60.     public class Weather  
  61.     {  
  62.         public int id { getset; }  
  63.         public string main { getset; }  
  64.         public string description { getset; }  
  65.         public string icon { getset; }  
  66.     }   
Sender Function
  1. using System;  
  2. using System.Net.Http;  
  3. using Microsoft.Azure.WebJobs;  
  4. using Microsoft.Azure.WebJobs.Host;  
  5. using Microsoft.Extensions.Logging;  
  6. using Newtonsoft.Json;  
  7.   
  8. namespace Sender  
  9. {  
  10.     public static class SenderFunction  
  11.     {  
  12.         [FunctionName("Sender")]  
  13.         public static async void Run([ServiceBusTrigger("Queue_Name", Connection = "Service_Bus_Connection")]string city, ILogger log)  
  14.         {  
  15.             // Send Request to Open Weather Map API  
  16.             var client = new HttpClient();  
  17.             var request = new HttpRequestMessage  
  18.             {  
  19.                 Method = HttpMethod.Get,  
  20.                 // Using string interpolation to add city to URL  
  21.                 RequestUri = new Uri($"https://rapidapi.p.rapidapi.com/weather?q={city}"),  
  22.                 Headers =  
  23.                 {  
  24.                     { "x-rapidapi-host""community-open-weather-map.p.rapidapi.com" },  
  25.                     { "x-rapidapi-key""API_KEY" },  
  26.                 },  
  27.             };  
  28.   
  29.             // Receive response  
  30.             WeatherResponse weatherResponse;  
  31.             using (var response = await client.SendAsync(request))  
  32.             {  
  33.                 response.EnsureSuccessStatusCode();  
  34.                 var body = await response.Content.ReadAsStringAsync();  
  35.                 // Deserialize response into WeatherResponse object  
  36.                 weatherResponse = JsonConvert.DeserializeObject<WeatherResponse>(body);  
  37.             }  
  38.   
  39.             // Do whatever you want with the response and it's object. For the sake of simplicity I'll just log it  
  40.             log.LogInformation($"Temperature in {city} is {weatherResponse.main.temp}K");  
  41.         }  
  42.     }  
  43. }  

Testing

 
Run the functions
 
Developing Middleware With Microsoft Azure Service Bus And Functions
 
Developing Middleware With Microsoft Azure Service Bus And Functions
 
Using Postman
 
Open Postman and make POST Http Request to Receiver Function endpoint.
 
Developing Middleware With Microsoft Azure Service Bus And Functions
 
Verify Results
 
Developing Middleware With Microsoft Azure Service Bus And Functions
 

Conclusion

 
As shown above, we can create middleware with Microsoft Azure Service Bus and Function. We can also add many other features to it such as Azure Application Insights for logging and performance monitoring, CosmosDB or Blob Storage for archiving data, we can also use Service Bus Topics if our scenario requires Publisher/Subscriber message model instead of simple queues. We can also expand our Receiver Service and Sender Service to cater to more communication scenarios. We can also do message transformation from System X to System Y such that message format is compatible with System Y using this approach.