O Sertão será Cloud

Discover the Secret to Connecting Your Azure Function to Cosmos DB Like a Pro!

Azure Cosmos DB is Microsoft’s NoSQL database, designed for high scalability and low latency, ideal for modern applications that demand quick responses and flexibility. Correctly configuring the Cosmos DB client is crucial to ensure security and efficiency, especially in serverless scenarios. In this article, I’ll show how to configure an Azure Function to connect to Cosmos DB using different authentication methods while applying Clean Code practices.

· What is Azure Cosmos DB NoSQL?
Key Features:
Common Use Cases:
· Azure Cosmos DB: Best Practices
1. Performance Efficiency:
2. Cost Management:
3. Security:
4. Reliability:
5. Operational Governance:
· Step 1: Environment Setup
· Step 2: Creating the CredentialProvider Abstraction
· Step 3: Implementing Credential Providers
Method 1: SystemAssignedManagedIdentityCredentialProvider
Method 2: UserAssignedManagedIdentityCredentialProvider
Method 3: ServicePrincipalCredentialProvider
· Step 4: Credential Providers Architecture
· Step 5: Using the Factory in an Azure Function
Example 1: Azure Function with System-Assigned Managed Identity
Example 2: Azure Function with User-Assigned Managed Identity
Example 3: Azure Function with Service Principal
· Conclusion

What is Azure Cosmos DB NoSQL?

When it comes to NoSQL databases, Azure Cosmos DB stands out. It’s a Microsoft service designed to offer high availability, low latency, and global scalability. What I love most is that it supports multiple data models, such as document, key-value, graph, and column-family, making it very flexible for different types of applications.

Key Features:

  1. Global Distribution: One of Cosmos DB’s strengths is its ability to replicate data across multiple Azure regions, ensuring fast user access regardless of location.
  2. Multi-Model: I love that it supports multiple APIs, including SQL (Core), MongoDB, Cassandra, Gremlin (for graphs), and Table API. This allows me to choose the model that best fits my project’s needs.
  3. Low Latency: It offers millisecond response times for both read and write operations, even at scale. This is essential for applications that need fast performance.
  4. Automatic Scalability: Another advantage is its ability to dynamically adjust throughput and storage without downtime. This means I can scale resources as application demands change.
  5. Security and Compliance: Cosmos DB includes advanced security features like encryption at rest and in transit, and complies with various standards and regulations, which is critical for me when handling sensitive data.

Common Use Cases:

I see Azure Cosmos DB widely used in:

  • Web and mobile applications that require fast and reliable performance.
  • Content management systems that need flexibility in data modeling.
  • Online games that demand low latency and scalability.
  • IoT applications generating large volumes of data.

Azure Cosmos DB: Best Practices

Implementing Azure Cosmos DB using the Azure Well-Architected Framework (WAF) and the Cloud Adoption Framework (CAF) principles can significantly improve the effectiveness and reliability of your database solution.

1. Performance Efficiency:

  • Throughput Optimization: Use the provisioned throughput model effectively, scaling resources based on your application’s demand. Monitor usage patterns and adjust throughput to maintain performance while controlling costs.
  • Data Partitioning: Design your data model with partitioning in mind to ensure balanced distribution and minimize request latency. Choose appropriate partition keys based on access patterns.

2. Cost Management:

  • Proper Scaling: Regularly evaluate and adjust the throughput and storage allocated to your Cosmos DB instances. Use Azure Cost Management tools to analyze expenses and identify optimization areas.
  • Free Tier and Scalability: If you’re just starting, take advantage of Cosmos DB’s free tier to explore and develop your application without incurring costs, and scale as needed.

3. Security:

  • Managed Identities: Use Azure Managed Identity for secure access to Cosmos DB without hardcoding credentials. This reduces the risk of credential exposure.
  • Network Security: Implement virtual network service endpoints or private links to restrict access to your Cosmos DB accounts, ensuring only trusted applications can connect.

4. Reliability:

  • Multi-Region Replication: Leverage Cosmos DB’s global distribution capabilities. Configure multi-region writes to ensure high availability and disaster recovery, enabling quick recovery in case of regional failures.
  • Backup Strategies: Perform regular data backups and implement automated backup policies to ensure data recovery in case of accidental loss or corruption.

5. Operational Governance:

  • Monitoring and Alerts: Use Azure Monitor and Application Insights to track performance metrics, configure alerts for anomalies, and ensure your Cosmos DB instances are running optimally.
  • Compliance: Ensure your Cosmos DB usage complies with organizational policies and regulatory requirements by using Azure Policy to enforce governance standards.

By integrating these practices into your use of Azure Cosmos DB, you can build robust, secure, and cost-effective applications that fully leverage the platform’s capabilities while aligning with WAF and CAF best practices.

Step 1: Environment Setup

Before starting, install the necessary dependencies:

npm install @azure/cosmos @azure/identity

Step 2: Creating the CredentialProvider Abstraction

Let’s create an interface CredentialProvider that defines a getCredential method.

import { TokenCredential } from "@azure/identity";
/**
* Interface for providing Azure credentials.
*/
interface CredentialProvider {
/**
* Retrieves the Azure credential.
* @returns {TokenCredential} The Azure credential.
*/
getCredential(): TokenCredential;
}

Step 3: Implementing Credential Providers

We’ll create different implementations of CredentialProvider for various authentication methods.

Method 1: SystemAssignedManagedIdentityCredentialProvider

import { DefaultAzureCredential } from "@azure/identity";
/**
* Provides credentials for system-assigned managed identity.
*/
class SystemAssignedManagedIdentityCredentialProvider implements CredentialProvider {
/**
* Retrieves the Azure credential for system-assigned managed identity.
* @returns {TokenCredential} The Azure credential.
*/
getCredential(): TokenCredential {
return new DefaultAzureCredential();
}
}

Method 2: UserAssignedManagedIdentityCredentialProvider

import { DefaultAzureCredential } from "@azure/identity";

/**
* Provides credentials for user-assigned managed identity.
*/
class UserAssignedManagedIdentityCredentialProvider implements CredentialProvider {
private clientId: string;
/**
* Creates an instance of UserAssignedManagedIdentityCredentialProvider.
* @param {string} clientId - The client ID of the user-assigned managed identity.
*/
constructor(clientId: string) {
this.clientId = clientId;
}
/**
* Retrieves the Azure credential for user-assigned managed identity.
* @returns {TokenCredential} The Azure credential.
*/
getCredential(): TokenCredential {
return new DefaultAzureCredential({
managedIdentityClientId: this.clientId
});
}
}

Method 3: ServicePrincipalCredentialProvider

import { ClientSecretCredential } from "@azure/identity";
/**
* Provides credentials for service principal authentication.
*/
class ServicePrincipalCredentialProvider implements CredentialProvider {
private tenantId: string;
private clientId: string;
private clientSecret: string;
/**
* Creates an instance of ServicePrincipalCredentialProvider.
* @param {string} tenantId - The tenant ID.
* @param {string} clientId - The client ID.
* @param {string} clientSecret - The client secret.
*/
constructor(tenantId: string, clientId: string, clientSecret: string) {
this.tenantId = tenantId;
this.clientId = clientId;
this.clientSecret = clientSecret;
}
/**
* Retrieves the Azure credential for service principal authentication.
* @returns {TokenCredential} The Azure credential.
*/
getCredential(): TokenCredential {
return new ClientSecretCredential(this.tenantId, this.clientId, this.clientSecret);
}
}

Step 4: Credential Providers Architecture

Here’s a factory to instantiate the Cosmos DB client using the CredentialProvider.

import { CosmosClient } from "@azure/cosmos";

/**
* @class CosmosClientFactory
* Factory responsible for creating instances of CosmosClient.
* This class abstracts the client creation logic, allowing the use
* of different credential providers for authentication.
*/
class CosmosClientFactory {
private endpoint: string;
private credentialProvider: CredentialProvider;

/**
* @constructor
* @param {string} endpoint - URL of the Azure Cosmos DB resource.
* @param {CredentialProvider} credentialProvider - Credential provider to be used.
*/
constructor(endpoint: string, credentialProvider: CredentialProvider) {
this.endpoint = endpoint;
this.credentialProvider = credentialProvider;
}

/**
* Creates a new instance of the CosmosClient with the provided credentials.
* @returns {CosmosClient} Configured instance of the CosmosClient.
*/
createCosmosClient(): CosmosClient {
const credential = this.credentialProvider.getCredential();
return new CosmosClient({ endpoint: this.endpoint, aadCredentials: credential });
}
}

Step 5: Using the Factory in an Azure Function

Let’s integrate this into an Azure Function using Managed Identity.

Example 1: Azure Function with System-Assigned Managed Identity

import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { CosmosClientFactory } from "./CosmosClientFactory";
import { SystemAssignedManagedIdentityCredentialProvider } from "./SystemAssignedManagedIdentityCredentialProvider";

const endpoint = process.env.AZURE_COSMOS_RESOURCEENDPOINT;

/**
* Azure Function using system-assigned managed identity to access Azure CosmoDB NoSQL.
*
* @param context - The Azure Function context.
* @param req - The HTTP request.
*
* @returns A promise that resolves when the function is complete.
*/
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
const credentialProvider = new SystemAssignedManagedIdentityCredentialProvider();
const cosmosClientFactory = new CosmosClientFactory(endpoint, credentialProvider);

const cosmosClient = cosmosClientFactory.createCosmosClient();
const database = cosmosClient.database("my-database");
const container = database.container("my-container");

const { resource } = await container.item("item-id").read();
context.res = {
status: 200,
body: resource
};
};

export default httpTrigger;

Example 2: Azure Function with User-Assigned Managed Identity

import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { CosmosClientFactory } from "./CosmosClientFactory";
import { UserAssignedManagedIdentityCredentialProvider } from "./UserAssignedManagedIdentityCredentialProvider";

const endpoint = process.env.AZURE_COSMOS_RESOURCEENDPOINT;
const clientId = process.env.AZURE_MYSQL_CLIENTID;


/**
* Azure Function using user-assigned managed identity to access Azure CosmoDB NoSQL.
*
* @param context - The Azure Function context.
* @param req - The HTTP request.
*
* @returns A promise that resolves when the function is complete.
*/
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
const credentialProvider = new UserAssignedManagedIdentityCredentialProvider(
clientId,
);
const cosmosClientFactory = new CosmosClientFactory(endpoint, credentialProvider);

const cosmosClient = cosmosClientFactory.createCosmosClient();
const database = cosmosClient.database("my-database");
const container = database.container("my-container");

const { resource } = await container.item("item-id").read();
context.res = {
status: 200,
body: resource
};
};

export default httpTrigger;

Example 3: Azure Function with Service Principal

import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { CosmosClientFactory } from "./CosmosClientFactory";
import { ServicePrincipalCredentialProvider } from "./ServicePrincipalCredentialProvider";

const endpoint = process.env.AZURE_COSMOS_RESOURCEENDPOINT;
const tenantId = process.env.AZURE_COSMOS_TENANTID;
const clientId = process.env.AZURE_COSMOS_CLIENTID;
const clientSecret = process.env.AZURE_COSMOS_CLIENTSECRET;

/**
* Azure Function using user-assigned managed identity to access Azure CosmoDB NoSQL.
*
* @param context - The Azure Function context.
* @param req - The HTTP request.
*
* @returns A promise that resolves when the function is complete.
*/
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
const credentialProvider = new ServicePrincipalCredentialProvider(
tenantId, clientId, clientSecret,
);
const cosmosClientFactory = new CosmosClientFactory(endpoint, credentialProvider);

const cosmosClient = cosmosClientFactory.createCosmosClient();
const database = cosmosClient.database("my-database");
const container = database.container("my-container");

const { resource } = await container.item("item-id").read();
context.res = {
status: 200,
body: resource
};
};

export default httpTrigger;

Conclusion

By applying Clean Code principles such as modularity and clarity, we created a scalable design to connect to Cosmos DB. With these guidelines and the implementation presented, you’ll be ready to build robust and secure solutions with Azure Cosmos DB in a serverless environment.

Stackademic 🎓

Thank you for reading until the end. Before you go:


Discover the Secret to Connecting Your Azure Function to Cosmos DB Like a Pro! was originally published in Stackademic on Medium, where people are continuing the conversation by highlighting and responding to this story.