Integrando o Padrão Singleton no NestJS com OCI Vault: Um Guia Detalhado
Introdução
O NestJS é um framework Node.js avançado que facilita a criação de aplicações modulares. Um padrão muito usado é o Singleton, ideal para gerenciar dependências únicas como serviços de configuração. Neste artigo, mostraremos como aplicar o padrão Singleton no NestJS e integrá-lo ao OCI Vault, o serviço da Oracle Cloud Infrastructure para gerenciamento seguro de segredos e chaves criptográficas.
O que é o Singleton?
O Singleton é um padrão de projeto que garante que uma classe tenha apenas uma instância única e fornece um ponto de acesso global a essa instância. Isso é útil quando precisamos de um recurso compartilhado em todo o aplicativo, como um gerenciador de configurações ou uma conexão com o banco de dados.
Os principais elementos de um Singleton incluem:
- Um construtor privado: Isso garante que a classe não possa ser instanciada de fora.
- Uma variável estática para armazenar a instância única: Ela armazena a instância da classe e garante que haja apenas uma instância.
- Um método estático para obter a instância única: Este método é usado para acessar a instância única, criando-a se ainda não existir.
Passo 1 - Implementando o Singleton no NestJS
Nesta seção, mostraremos como implementar um Singleton no NestJS. Para isso, criaremos um serviço de gerenciamento de configurações responsável por carregar as configurações do aplicativo e fornecer acesso a elas. O processo envolve as seguintes etapas:
nest generate service configurationPasso 2 - Implemente a classe ConfigurationService como um Singleton:
// configuration.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class ConfigurationService {
private static instance: ConfigurationService;
private readonly config: Record<string, any>;
private constructor() {
this.config = {
// Carregue suas configurações aqui
};
}
public static getInstance(): ConfigurationService {
if (!ConfigurationService.instance) {
ConfigurationService.instance = new ConfigurationService();
}
return ConfigurationService.instance;
}
public getConfig(key: string): any {
return this.config[key];
}
}Passo 3 - Use o Singleton no seu aplicativo:
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigurationService } from './configuration/configuration.service';
@Module({
providers: [
{
provide: ConfigurationService,
useValue: ConfigurationService.getInstance(),
},
],
})
export class AppModule {}
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { ConfigurationService } from './configuration/configuration.service';
@Controller()
export class AppController {
constructor(private readonly configService: ConfigurationService) {}
@Get('config')
getConfig(): Record<string, any> {
return this.configService.getConfig('exampleConfig');
}
}Passo 4 - Integrando o OCI Vault
O OCI Vault permite o armazenamento seguro de segredos como tokens e senhas. Para acessá-lo via Node.js, usaremos o SDK da OCI.
Pré-requisitos:
- Um Vault configurado no OCI.
- Uma Master Encryption Key no vault.
- Uma Secret criada e ativa.
- Permissões definidas via IAM policies.
Passo 5 — Instalando o SDK da OCI
npm install oci-sdkPasso 6 - Criando o SecretClient da OCI:
- Atualize o arquivo para a criação do
SecretCliente recebê-lo como dependência:
// configuration/oci-secret-client.provider.ts
import { Provider } from '@nestjs/common';
import * as oci from 'oci-sdk';
export const OciSecretClientProvider: Provider = {
provide: 'OCI_SECRET_CLIENT',
useFactory: () => {
const provider = new oci.Common.ConfigFileAuthenticationDetailsProvider();
const secretsClient = new oci.vault.VaultsClient({
authenticationDetailsProvider: provider,
});
return secretsClient;
},
};Aqui, definimos um provider para o SecretClient, que será usado para criar a instância do SecretClient com as configurações corretas.
Passo 7 - Atualize o ConfigurationService para usar o OCI Vault:
// configuration.service.ts
import { Injectable } from '@nestjs/common';
import { VaultsClient } from 'oci-sdk/lib/vault';
@Injectable()
export class ConfigurationService {
private static instance: ConfigurationService;
private constructor(private readonly secretsClient: VaultsClient) {}
public static getInstance(secretsClient: VaultsClient): ConfigurationService {
if (!ConfigurationService.instance) {
ConfigurationService.instance = new ConfigurationService(secretsClient);
}
return ConfigurationService.instance;
}
public async getConfig(secretId: string): Promise<string | null> {
try {
const response = await this.secretsClient.getSecretBundle({
secretId,
});
const secretContent = response.secretBundle.secretBundleContent as any;
const base64Decoded = Buffer.from(secretContent.content, 'base64').toString('utf-8');
return base64Decoded;
} catch (error) {
console.error(`Erro ao buscar segredo ${secretId}: ${error.message}`);
return null;
}
}
}Passo 8 - Atualize o AppModule:
Atualize o arquivo app.module.ts para incluir o SecretClientProvider e passar o SecretClient para a função getInstance() da ConfigurationService:
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigurationService } from './configuration/configuration.service';
import { OciSecretClientProvider } from './configuration/oci-secret-client.provider';
import { VaultsClient } from 'oci-sdk/lib/vault';
@Module({
providers: [
OciSecretClientProvider,
{
provide: ConfigurationService,
useFactory: (vaultsClient: VaultsClient) =>
ConfigurationService.getInstance(vaultsClient),
inject: ['OCI_SECRET_CLIENT'],
},
],
})
export class AppModule {}Boas práticas e considerações
Ao trabalhar com o padrão Singleton e o NestJS, é importante ter em mente as seguintes boas práticas e considerações:
- Use Singleton somente quando necessário.
- Prefira injeção de dependência via Providers para manter testabilidade.
- Utilize variáveis de ambiente para armazenar OCIDs e dados sensíveis.
- Garanta políticas corretas no OCI IAM (para leitura de Vaults e Secrets).
- Evite deixar segredos hardcoded no código.
Conclusão
Neste artigo, discutimos em detalhes como aplicar o padrão Singleton no NestJS e integrá-lo ao OCI Vault. Ao ajustar nossa implementação para usar providers e seguir boas práticas, criamos um aplicativo modular e seguro.
