This is Part 6 of a 6-part series that demonstrates how to take a monolithic Python application and gradually modernize both the application and its data using Azure tools and services. This article discusses how to start making the app more cloud-native, and demonstrates how to begin moving the legacy app’s functionality into Azure function-based microservices.
Throughout this series, we’ve explored how to modernize a real-world Django legacy app in many ways.
In the first article, we replaced the SQLite database with PostgreSQL and populated it with sample data. The second article demonstrated setting up an Azure Database for PostgreSQL and migrating the app’s data to Azure.
We created the app via the Azure Portal in the third article and deployed it using the VS Code Azure App Service extension. The fourth article showed how to containerize our legacy Python app, create an AKS cluster, and deploy it using Visual Studio Code.
In the fifth article, we created a Cosmos DB database, then adapted our app to work with it using the Djongo Mapper and Cosmos DB’s MongoDB API.
In this final article of the series, we’ll explore how to start making the app more cloud-native. We’ll demonstrate how to move the legacy app’s functionality into an Azure Serverless Function app.
You can follow the steps in this article to get your application running or download and open this GitHub repository folder to get the final project. You should know Python and Django to follow along.
Benefits of Azure Serverless Functions
The Microsoft Azure Functions serverless compute service enables developers to build applications faster. They don't have to worry about setting up or maintaining infrastructure. Serverless app development also enables developers to create code that runs natively on a cloud platform.
The pricing model is another serverless architecture benefit. With Microsoft Azure Functions, developers can allocate resources on-demand with no upfront costs or commitments. Azure Functions are highly available, and you can configure them to scale automatically as your user base grows and use increases.
Azure Functions are best suited for small apps like our Django-based backend app, which can work independently of other websites.
Setting Up an Azure Function App
We’ll create an Azure Function App, either directly through the Azure Portal or using a VS Code extension, allowing us to interact with Azure Functions directly from our favorite IDE.
Open VS Code to access the code from the previous article of this series. Click the Extensions tab, as shown in the screenshot below. Then, search for “Azure Functions” and install that package.
The Azure Functions extension lets you quickly create, debug, manage, and deploy serverless apps from within VS Code. These tools integrate seamlessly with your Azure account, enabling rapid development and straightforward deployment.
Now it’s time to create our Azure Function. Select the Azure tab and click the Create Function icon within the Functions section.
We still don’t have a Function
project on Azure, though. We’ll create it locally now, then upload it to the cloud later.
A popup now asks if you would like to create a new project. Click Yes.
Azure functions support numerous programming languages, like Python, JavaScript, TypeScript, C#, PowerShell, and Java. Choose Python.
Now we choose our Function project’s template. There are many forms of Azure Functions triggers for various uses, depending on when the functions should run. For example, you might want a function to run once a day at 6 AM, or whenever a message queue processes an item.
In our app, we want HTTP triggers that will respond to HTTP requests from the Teams tab, so choose that option.
Now we name the Azure function “az-function-conduit
”:
Then, we provide the authorization level. We make it public by choosing Anonymous.
Next, popups will ask whether to overwrite the .gitignore and requirements.txt files. Answer No to both questions.
We now have our first Azure function.
Now, we select the Functions group and click the Deploy to the Function App button:
We still have no Function App in Azure, so we create it by clicking the first option, + Create new Function App in Azure.
Name the project “conduit-function-app
”.
Notice how VS Code now displays a new folder where you can conveniently browse data from your Function App stored in the Azure cloud. This includes files, logs, and settings.
Click the Explorer tab to view your source code. Note that:
- Our Azure Function is in a separate Python module.
- The host.json metadata file contains our Function’s configuration in a Function App instance.
- The local.settings.json file provides the settings when the app runs locally.
We must ensure Python support for Azure Functions next. So, open the requirements.txt file and add this line:
azure-functions
Now that our az-function-conduit
function is ready, we can debug it locally to ensure it’s working as we expect. Click on the debug icon and select Attach to Python Functions, or press the F5 key.
As soon as you start debugging, the terminal window appears, and a URL hosts the function locally.
Open this URL in a browser window. Since our function app is virtually empty, the browser displays a standard template message for Azure Functions:
Running Django Inside an Azure Function
At this point, we have a Conduit module (based on the Django REST framework) and an Azure Function module in the same project. Since this article aims to demonstrate how to move our Django app into Azure Functions, we could migrate code from the Conduit module to the az-function-conduit
app. However, this approach takes a lot of work without much benefit.
Fortunately, Django implements Web Server Gateway Interface (WSGI). When deployed to a web server, WSGI enables you to forward requests to web apps written in Python. We can take advantage of WSGI by keeping the Django code inside the Conduit module untouched and telling the Azure Function module to redirect the invocations to the Django handler.
So, open the \az-function-conduit\__init__.py file and replace the code there with the following snippet:
import logging
import azure.functions as func
from conduit.wsgi import application
def main(req: func.HttpRequest, context: func.Context) -> func.HttpResponse:
return func.WsgiMiddleware(application).handle(req, context)
The WsgiMiddleware
method adapts a WSGI-supported Python server framework such as Django into Azure Functions in the code above. We can use it by calling the .handle()
function or exposing the .main
property in a HttpTrigger
event.
Next, open the \az-function-conduit\function.json file and add this line:
"route": "{*route}"
Like this:
{
"scriptFile": "__init__.py",
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
],
"route": "{*route}"
},
{
"type": "http",
"direction": "out",
"name": "$return"
}
]
}
Then open the \conduit\settings.py file and replace the ALLOWED_HOSTS
to enable any host to serve the application:
ALLOWED_HOSTS = ['*']
Defining the Application Settings
Using environment variables is a common way to define an app’s configuration, depending on its environment.
Since the database configurations depend on the project's Azure credentials, it's beneficial to modify the configuration without touching the app's source code and keep secrets outside the source code repository.
So, now open the \conduit\settings.py file, where our database settings are located, and use Python’s os
module to access the environment settings as follows:
DATABASES = {
'default': {
'ENGINE': 'djongo',
'ENFORCE_SCHEMA': False,
'NAME': os.getenv('DATABASE_NAME'),
'CLIENT': {
'host': os.getenv('DATABASE_HOST'),
'port': 10255,
'username': os.getenv('DATABASE_USER'),
'password': os.getenv('DATABASE_PASSWORD'),
'authMechanism': 'SCRAM-SHA-1',
'ssl': True,
'tlsAllowInvalidCertificates': True,
'retryWrites': False
}
}
}
Then, open Azure Portal and locate your Azure Cosmos DB API for MongoDB account. Click the Connection String tab to see the database configuration keys that we’ll use in our app.
Back in VS Code, click the Azure tab, select conduit-function-app
, and expand the Application Settings node.
Right-click the Application Settings node and select Add New Setting.
You’ll provide a new setting key and value here. Repeat this step for each application setting name and its value.
DATABASE_NAME
: conduit_db
DATABASE_HOST
: <<your-azure-cosmos-account>>.mongo.cosmos.azure.com DATABASE_USER
: <<your-azure-cosmos-account>> DATABASE_PASSWORD
: <<your-azure-cosmos-password>>
The new application settings will appear in a list, like the screenshot below:
Now it’s time to upload these database configurations to your Azure Function app. Right-click Application Settings and choose the Upload Local Settings menu item.
Testing the Azure Function App
Now that we’ve uploaded our database settings to the cloud, let’s test the Conduit app running on the Azure Function. Open the Azure Portal and search for and click Function App:
Next, click your Azure function app’s name.
In the Overview tab, click the URL link.
This action opens a new browser tab with the Azure Functions app’s base address.
To list the articles, add /api/articles to the URL like this:
https://<<YOUR-FUNCTION-APP>>.azurewebsites.net/api/articles
Next, navigate to the /api/articles/create-a-new-implementation-ukwcnm path to browse a specific article’s details, like this:
https:// <<YOUR-FUNCTION-APP>>.azurewebsites.net/api/articles/create-a-new-implementation-ukwcnm
To view that particular article’s comments, navigate to the /api/articles/create-a-new-implementation-ukwcnm/comments path:
https:// <<YOUR-FUNCTION-APP>>.azurewebsites.net/api/articles/create-a-new-implementation-ukwcnm/comments
Our legacy app’s articles are now available in the cloud via an Azure Function app.
Conclusion
In this final article in the Modernizing Python Apps and Data on Azure series, we discussed moving the legacy app into Azure Serverless Functions. This new approach had some fundamental differences in using the Django Rest Framework compared to the previous articles. Instead of hosting the Django app in an Azure App Service, where we can build, deploy, and scale web apps on a fully-managed platform, we used Azure Functions to listen and respond to events such as HTTP requests.
In moving our app into a serverless compute service, we learned how to create and configure an Azure Function in a Function App. Then we used a WSGI middleware to adapt the Django Rest framework app into the Azure Function.
Integrating your Django Rest Framework app into an Azure Function empowers you to write less code, maintain less infrastructure, and save money. You can avoid the inconvenience of deploying and maintaining servers as the Azure infrastructure provides all the up-to-date resources needed to keep your application running.
Moving a legacy application into the cloud may seem daunting at first. However, by breaking the task into stages and employing handy tools, including Azure’s many options, you can modernize your app to become low-maintenance, flexible, scalable, and resilient in the cloud.
To learn more about how to build and deploy your Python apps in the cloud, check out Python App Development - Python on Azure.