Como Configurar o Azure Blob Service Client Aplicando os Princípios SOLID em uma Azure Function com TypeScript
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:
- Single Responsibility Principle (SRP): Cada classe deve ter uma única responsabilidade.
- Open/Closed Principle (OCP): As classes devem estar abertas para extensão, mas fechadas para modificação.
- Liskov Substitution Principle (LSP): As subclasses devem poder substituir suas classes base.
- Interface Segregation Principle (ISP): Muitas interfaces específicas são melhores do que uma interface genérica.
- 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.