DEV Community

Danny
Danny

Posted on

Interlúdio Técnico #1 - OracleDB no JavaScript

Introdução

Oi pessoal! Perdão o sumiço, como falei, ainda estou estabelecendo uma rotina que dá conta de tudo. Pensei muito em criar ou não esse tipo de artigo e decidi ir em frente. Os interlúdios técnicos serão artigos menores focados em soluções, dúvidas, descobertas pontuais que vão surgindo no dia-a-dia, como pequenas pílulas de conhecimento. Na nossa bolha de desenvolvedores temos muitas pessoas excelentes produzindo conteúdo de altíssima qualidade em formatos diferentes: seja no (Atualmente bloqueado) X/Twitter, BlueSky, Threads, aqui no dev.to, no YouTube e outras plataformas. Nesse momento não consigo acompanhar uma produção específica para uma plataforma, então vou escrevendo aqui e repostando nas redes, desculpem o transtorno e peço compreensão. Bora lá.

Para esse primeiro interlúdio, decidi escrever sobre um querido do mundo enterprise, um banco que para javeiros é mais um dia qualquer, mas pode assombrar devs em outras stacks. Hoje vou falar do OracleDB, ou mais especificamente como construir um backend ou função Lambda que utilize OracleDB em JavaScript/TypeScript.

Por que OracleDB?

Não me pergunte, eu era uma criança quando isso ocorreu. O que posso dizer é que em mais de uma firma para qual trabalhei, os sistemas legados utilizam esse banco, e na modernização desses sistemas, seja uma refatoração apenas de código ou também de arquitetura, há alguns impecilhos para uma implementação de um backend JavaScript/TypeScript.

Drivers proprietários e o node-oracledb

Um banco Oracle em produção geralmente utiliza os recursos proprietários do banco para estabalecer conexões e dentre eles está o NNE (Native Network Encryption ou Criptograifa Nativa de Rede). Esse é o vilão da história, pois o node-oracledb traz apenas um cliente básico, ou como denominado pela prórpia Oracle, o thin-mode ou modo-magro na tradução ao pé da letra. Faz sentido o magro nesse contexto pois essa bilbioteca não traz o driver "cheio" da Oracle, que possui suporte ao NNE.

Instalando o driver em um backend TypeScript/JavaScript

Acredito que em um setup bem simples (como um sistema de arquivos ou armazenamento estático para guardar o código e uma instância de computação), os drivers podem ser copiados diretamente com o código fonte ou compilado e o carregamento do driver configurado para o caminho dos binários do driver, porém aqui vou trabalhar com uma solução baseada em Docker, pois vai garantir reprodutibilidade para deploys em Kubernetes, serviços Cloud baseados em Kubernetes, serviços cloud baseados em imagem Docker e instância de computação e serviços Cloud serverless que suportam imagens Docker.

Para manuais, mais informações e documentação oficial, vou disponibilizar uma lista abaixo com os links mais úteis:

Observações

O nome oficial do driver "cheio" (thick-mode) da Oracle é Instant Client.
Embora sejam disponiblizadas opções Docker com o Client pré-instalado, você pode encontrar problemas caso utilize uma Dockerfile multi-estágios, isso porque o Oracle Linux trabalha com binários compilados com MUSL, enquanto outras distribuições (inclusive as mais utilizadas para rodar o código final) utilizam GLIBC. Novamente, para fins de reprodutibilidade, demonstrarei aqui o caminho que em diferentes firmas foi a solução adotada, que consiste em baixar o Instant Client e copiá-lo para um diretório dentro da imagem Docker.

Dockerizando a aplicação

Vamos iniciar com o caso mais simples: você possui a liberdade para utilizar o Oracle Linux. Vamos assumir um ponto de entrada index.js.

FROM oraclelinux:8

RUN  dnf -y install oracle-instantclient-release-el8 && \
    dnf -y install oracle-instantclient-basic oracle-instantclient-devel oracle-instantclient-sqlplus && \
    rm -rf /var/cache/dnf

RUN dnf -y module enable nodejs:18 && \
    dnf -y install nodejs npm && \
    rm -rf /var/cache/dnf

WORKDIR /app

COPY . .

RUN npm install
RUN npm run build

ARG PORT=3000
EXPOSE $PORT

CMD [ "npm", "run", "start" ]
Enter fullscreen mode Exit fullscreen mode
// index.js
import { initOracleClient } from 'oracledb'

initOracleClient();

Enter fullscreen mode Exit fullscreen mode

Como a imagem já possui os caminhos (paths) todos preparados, esse setup já é suficiente para inicializar o driver.

Agora, assumindo que não seja possível utilizar a imagem oraclelinux, o mesmo resultado pode ser atingido com um estágio para baixar o Instant Client:

# Neste estágio são instaladas as dependências do projeto
FROM node:18-alpine AS deps
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

# Neste, o projeto é compilado. A separação desse estágio da
# instalação das dependências presume um processo de empacotamento
# (bundling) do código com as dependências necessárias, de forma a
# evitar uma pasta node_modules na imagem final
FROM node:18-alpine AS builder
WORKDIR /app

COPY . .
COPY --from=deps /app/node_modules ./node_modules

RUN npm run build

FROM node:18-alpine as download
WORKDIR /app

# São instalados os pacotes necessários para baixar e extrair
# os binários
RUN apk add --no-cache wget unzip
# A escolha de versão aqui (não é a última) se dá pela versão do
# GLIBC utilizada
RUN wget --no-cookies https://download.oracle.com/otn_software/linux/instantclient/2115000/instantclient-basic-linux.x64-21.15.0.0.0dbru.zip
# Aqui é criado o diretório para expandir o arquivo .zip e é
# executada a extração
RUN mkdir -p instantclient
RUN unzip instantclient-basic-linux.x64-21.15.0.0.0dbru.zip -d instantclient
# Por final, o arquivo .zip é apagado
RUN rm instantclient-basic-linux.x64-21.15.0.0.0dbru.zip
# Todos os comandos aqui devem ser concatenadaos utilizando '&&'
# e quebra de linha (\), optei pela verbosidade para comentar a
# solução linha por linha

FROM node:18-alpine as runner

# Aqui são configurados os caminhos de ambiente para o driver
ENV PORT=3000 PATH=$PATH:$ORACLE_HOME LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/oracle/instantclient_21_15
WORKDIR /app
RUN apk add --no-cache libc6-compat

# Finalmente, o código compilado e empacotado é copiado para o
# estágio final, bem como o Instant Client.
# A escolha do diretório /opt se dá por uma convenção, pois é
# o mesmo diretório utilizado am ambientes AWS Lambda para
# carregar camadas e arquivos complementares
COPY --from=builder /app/<seu diretório de output da build> ./
# Como não existirá uma pasta node_modules no estágio final, são
# copiados os binários do node-oracledb também
COPY --from=builder /app/node_modules/oracledb/build /opt/build
COPY --from=download /app/instantclient_21_15 /opt/oracle/instantclient_21_15

CMD ["npm", "run", "start"]
Enter fullscreen mode Exit fullscreen mode
// index.js
import { initOracleClient } from 'oracledb'

initOracleClient({ binaryDir: '/opt/build/Release', libDir: '/opt/oracle/instantclient_21_15' });
Enter fullscreen mode Exit fullscreen mode

Conclusão

A partir desse setup mínimo, seu serviço ou API está preparado para utilizar um banco Oracle com NNE, lembrando que o driver deve ser inicalizado antes de tentar se conectar ou formar uma piscina (pool) de conexões para utilizar o banco. Não desenvolvi uma solução de exemplo para este artigo pela diversidade de aplicações possíveis e suas bibliotecas e frameworks desenhados para cada tipo de solução, pessoalmente já desenvolvi tanto APIs REST quanto serviços engatilhados por eventos utilizando configurações parecidas.
Nada nesse artigo é original: tudo pode ser encontrado em perguntas e respostas no StackOverflow, pode ser desenvolvido a partir de um rascunho de prompts de um ChatGPT ou Claude, minha missão aqui é produzir um ponto de partida em Português e condensar a experiência.

Novamente, agradeço a leitura e a atenção! Até breve.

Top comments (0)