Como Configurar o Azure Blob Service Client Aplicando os Princípios SOLID em uma Azure Function com TypeScript

Cláudio Rapôso
6 min readJul 13, 2024

O Azure Blob Storage é uma solução de armazenamento de objetos da Microsoft que permite armazenar grandes quantidades de dados não estruturados. A configuração adequada do BlobServiceClient é crucial para garantir segurança e eficiência. Neste artigo, vamos demonstrar como configurar uma Azure Function para se autenticar no Azure Blob Storage utilizando diferentes métodos de autenticação (Managed Identity atribuída ao sistema, Managed Identity atribuída ao usuário e Service Principal) aplicando os princípios SOLID.

Princípios SOLID

Antes de começarmos a implementação, vamos revisar brevemente os princípios SOLID:

  1. Single Responsibility Principle (SRP): Cada classe deve ter uma única responsabilidade.
  2. Open/Closed Principle (OCP): As classes devem estar abertas para extensão, mas fechadas para modificação.
  3. Liskov Substitution Principle (LSP): As subclasses devem poder substituir suas classes base.
  4. Interface Segregation Principle (ISP): Muitas interfaces específicas são melhores do que uma interface genérica.
  5. Dependency Inversion Principle (DIP): Dependa de abstrações, não de implementações concretas.

Passo 1: Configuração do Ambiente

Antes de começar, certifique-se de ter as seguintes dependências instaladas:

npm install @azure/identity @azure/storage-blob

Passo 2: Criando a Abstração CredentialProvider

Vamos criar uma interface CredentialProvider que define um método getCredential.

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

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

Passo 3: Implementando Provedores de Credenciais

Criaremos diferentes implementações de CredentialProvider para vários métodos de autenticação.

Método 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();
}
}

Método 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
});
}
}

Método 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);
}
}

Passo 4: Criando a Fábrica BlobServiceClientFactory

Agora vamos criar uma fábrica que utiliza o CredentialProvider para criar instâncias de BlobServiceClient.

import { BlobServiceClient } from "@azure/storage-blob";

/**
* Factory for creating instances of BlobServiceClient.
*/
class BlobServiceClientFactory {
private accountUrl: string;
private credentialProvider: CredentialProvider;

/**
* Creates an instance of BlobServiceClientFactory.
* @param {string} accountUrl - The account URL for the BlobServiceClient.
* @param {CredentialProvider} credentialProvider - The provider for Azure credentials.
*/
constructor(accountUrl: string, credentialProvider: CredentialProvider) {
this.accountUrl = accountUrl;
this.credentialProvider = credentialProvider;
}

/**
* Creates a BlobServiceClient instance.
* @returns {BlobServiceClient} The BlobServiceClient instance.
*/
createBlobServiceClient(): BlobServiceClient {
const credential = this.credentialProvider.getCredential();
return new BlobServiceClient(this.accountUrl, credential);
}
}

Passo 5: Utilizando a Fábrica em uma Azure Function

Vamos ver como podemos utilizar a fábrica para criar uma instância do TableServiceClient dentro de uma Azure Function. Vamos demonstrar três exemplos: Managed Identity atribuída ao sistema, Managed Identity atribuída ao usuário e Service Principal.

Exemplo 1: Azure Function com Managed Identity Atribuída ao Sistema

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

/**
* Azure Function triggered by HTTP request.
* Retrieves an image from Azure Blob Storage using system-assigned managed identity.
* @param context The Azure Function context.
* @param req The HTTP request.
*/
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
const accountUrl = process.env.AZURE_STORAGEBLOB_RESOURCEENDPOINT;
const credentialProvider = new SystemAssignedManagedIdentityCredentialProvider();
const blobServiceClientFactory = new BlobServiceClientFactory(accountUrl, credentialProvider);

const blobServiceClient = blobServiceClientFactory.createBlobServiceClient();
const containerClient = blobServiceClient.getContainerClient("my-container");
const blockBlobClient = containerClient.getBlockBlobClient("my-image.jpg");
const downloadBlockBlobResponse = await blockBlobClient.download(0);
const imageBuffer = await streamToBuffer(downloadBlockBlobResponse.readableStreamBody);

context.res = {
status: 200,
headers: {
'Content-Type': 'image/jpeg'
},
body: imageBuffer
};
};

/**
* Converts a readable stream into a buffer.
* @param readableStream The readable stream to convert.
* @returns A Promise resolving to the buffer containing stream data.
*/
async function streamToBuffer(readableStream): Promise<Buffer> {
return new Promise<Buffer>((resolve, reject) => {
const chunks = [];
readableStream.on("data", (data) => {
chunks.push(data instanceof Buffer ? data : Buffer.from(data));
});
readableStream.on("end", () => {
resolve(Buffer.concat(chunks));
});
readableStream.on("error", reject);
});
}

export default httpTrigger;

Exemplo 2: Azure Function com Managed Identity Atribuída ao Usuário

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

/**
* Azure Function triggered by HTTP request.
* Retrieves an image from Azure Blob Storage using user-assigned managed identity.
* @param context The Azure Function context.
* @param req The HTTP request.
*/
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
const accountUrl = process.env.AZURE_STORAGEBLOB_RESOURCEENDPOINT;
const clientId = process.env.AZURE_STORAGEBLOB_CLIENTID;
const credentialProvider = new UserAssignedManagedIdentityCredentialProvider(clientId);
const blobServiceClientFactory = new BlobServiceClientFactory(accountUrl, credentialProvider);

const blobServiceClient = blobServiceClientFactory.createBlobServiceClient();
const containerClient = blobServiceClient.getContainerClient("my-container");
const blockBlobClient = containerClient.getBlockBlobClient("my-image.jpg");
const downloadBlockBlobResponse = await blockBlobClient.download(0);
const imageBuffer = await streamToBuffer(downloadBlockBlobResponse.readableStreamBody);

context.res = {
status: 200,
headers: {
'Content-Type': 'image/jpeg'
},
body: imageBuffer
};
};

/**
* Converts a readable stream into a buffer.
* @param readableStream The readable stream to convert.
* @returns A Promise resolving to the buffer containing stream data.
*/
async function streamToBuffer(readableStream): Promise<Buffer> {
return new Promise<Buffer>((resolve, reject) => {
const chunks = [];
readableStream.on("data", (data) => {
chunks.push(data instanceof Buffer ? data : Buffer.from(data));
});
readableStream.on("end", () => {
resolve(Buffer.concat(chunks));
});
readableStream.on("error", reject);
});
}

export default httpTrigger;

Exemplo 3: Azure Function com Service Principal

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

/**
* Azure Function triggered by HTTP request.
* Retrieves an image from Azure Blob Storage using service principal authentication.
* @param context The Azure Function context.
* @param req The HTTP request.
*/
const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
const accountUrl = process.env.AZURE_STORAGEBLOB_RESOURCEENDPOINT;
const tenantId = process.env.AZURE_STORAGEBLOB_TENANTID;
const clientId = process.env.AZURE_STORAGEBLOB_CLIENTID;
const clientSecret = process.env.AZURE_STORAGEBLOB_CLIENTSECRET;
const credentialProvider = new ServicePrincipalCredentialProvider(tenantId, clientId, clientSecret);
const blobServiceClientFactory = new BlobServiceClientFactory(accountUrl, credentialProvider);

const blobServiceClient = blobServiceClientFactory.createBlobServiceClient();
const containerClient = blobServiceClient.getContainerClient("my-container");
const blockBlobClient = containerClient.getBlockBlobClient("my-image.jpg");
const downloadBlockBlobResponse = await blockBlobClient.download(0);
const imageBuffer = await streamToBuffer(downloadBlockBlobResponse.readableStreamBody);

context.res = {
status: 200,
headers: {
'Content-Type': 'image/jpeg'
},
body: imageBuffer
};
};

/**
* Converts a readable stream into a buffer.
* @param readableStream The readable stream to convert.
* @returns A Promise resolving to the buffer containing stream data.
*/
async function streamToBuffer(readableStream): Promise<Buffer> {
return new Promise<Buffer>((resolve, reject) => {
const chunks = [];
readableStream.on("data", (data) => {
chunks.push(data instanceof Buffer ? data : Buffer.from(data));
});
readableStream.on("end", () => {
resolve(Buffer.concat(chunks));
});
readableStream.on("error", reject);
});
}

export default httpTrigger;

Conclusão

Aplicando os princípios SOLID, criamos um design modular e extensível para a configuração do BlobServiceClient. Este design facilita a adição de novos métodos de autenticação e a manutenção do código. Seguindo esses passos, você pode garantir que seu código seja mais organizado, fácil de entender e de manter.

Referencias

--

--

Cláudio Rapôso
Cláudio Rapôso

Written by Cláudio Rapôso

Microsoft MVP | Software Architect | Teacher | Book Author | MCT | 12x Microsoft Certified Connect with me in https://www.linkedin.com/in/cfraposo

No responses yet