Gabriel / Ramos

⟨ pintor de pixel ⟩

  • Blog
  • Variáveis de ambiente e ciclo de desenvolvimento

Variáveis de ambiente e ciclo de desenvolvimento

Um pouco sobre tempo de desenvolvimento, construção, execução e algumas práticas pra consultar valores em sua aplicação como variável de ambiente

7 min. de leitura

Foto por Pritesh Sudra

Em todos esses ambientes e principalmente no momento de executar nossa aplicação, precisamos lidar com algumas variáveis que podem alterar a forma como nosso código se comporta ou é consumido pelo público final.

Se nosso serviço salva um registro em um banco, não queremos manter os dados de nosso servidor local quando esse serviço estiver em produção.

Se nossa aplicação renderiza imagens a partir de uma URL diferente dependendo do ambiente em que estiver sendo executada, devemos conseguir mudar essa URL pra ter um ambiente mais consistente e controlado.

Trabalhar com essas “variáveis de ambiente” é algo bem comum, e é interessante pensar um pouco no ciclo de desenvolvimento do software que escrevemos pra entender melhor essas variáveis.

#O ciclo de desenvolvimento

Se pensamos num “ciclo de desenvolvimento” de uma aplicação simples, que pode ser acessada a qualquer momento em algum servidor rodando aí, de forma bem simples e direta ao ponto, podemos imaginar algumas etapas. Algumas delas são:

  • desenvolvimento;
  • compilação;
  • execução.

Isso é apenas uma visão geral de algumas etapas que, provavelmente, vão existir no seu sistema. Não quer dizer que seu fluxo seja somente baseado nessas etapas. Elas servem apenas pra guiar nosso pensamento no post de hoje.

#Desenvolvimento

É o momento em que um projeto e seu código-fonte está sendo escrito, geralmente, de forma local, na máquina de quem desenvolve.

#Compilação

O momento em que uma aplicação está sendo preparada para execução. Em linguagens interpretadas, como JavaScript ou Python, geralmente é uma etapa que não acontece, a menos que você esteja usando alguma tecnologia como Babel, TypeScript ou algo que necessite “preparar” (compilar) seu código pra ser executado em um determinado ambiente.

Em outras linguagens, é comum ocorrer um processo de compilação e é fundamental para que já possamos pegar alguns erros antes mesmos de executarmos nosso código.

Um compilador, a grosso modo, basicamente “traduz” um código de uma linguagem, para outra linguagem.

No caso do TypeScript, o compilador será o responsável por produzir o código em JavaScript utilizado na sua aplicação final. No caso do Babel, ele simplesmente escreve um código em JavaScript de versões diferentes (até por isso o termo correto é transpilador e não compilador).

Em outras linguagens, como Golang, o compilador gera um código executável diretamente em binário, pronto pra ser executado no ambiente de destino.

#Execução

Após desenvolvimento e preparação, é hora de executar sua aplicação. Então essa, pra nosso caso, será a última etapa do ciclo de desenvolvimento que estamos imaginando aqui.

#Variáveis de ambiente

Como comentamos anteriormente, são valores que permitem que controlemos alguns comportamentos predefinidos nos nossos sistemas.

Essas variáveis são fornecidas à aplicação de duas formas diferentes: através da sessão de terminal que está executando o processo, ou de maneira um pouco mais manual.

Em JavaScript (NodeJS) podemos acessar todas as variáveis de ambiente de uma aplicação através de process.env e também podemos fornecer uma nova variável quando estamos rodando nosso programa ao, simplesmente, colocar seu valor antes do comando que o executa.

Vamos imaginar que executamos o comando node arquivo.js pra iniciar nossa aplicação. Se colocarmos um valor antes desse comando como nome=gabriel podemos criar uma variável de ambiente nome com valor de gabriel.

Faça o teste por aí, no seu terminal, execute node e dentro do ambiente do repl (ambiente de execução e feedback em tempo real do Node) execute process.env.nome. Inicialmente esse valor deverá estar vazio (caso você não tenha essa variável de ambiente exportada no seu terminal, claro). Depois coloque nome=<seu-nome> node e tente acessar a mesma variável.

Existe um padrão onde variáveis de ambiente são escritas em letras maiúsculas e utilizando underline (_) para caracter separador. Então o correto, caso tivéssemos uma variável nome seria nomeá-la NOME e caso fosse composta por mais de uma palavra, como nome completo, algo como NOME_COMPLETO. O caracter de espaço serve pra fornecer mais de uma variável. Por exemplo, NOME=gabriel SOBRENOME=ramos node arquivo.js irá fornecer a variável NOME e SOBRENOME com seus respectivos valores para o processo que vai executar o arquivo.js. Já seu valor é livre, mas será interpretado como um texto pelo seu processo, então cabe ao seu programa realizar qualquer conversão necessária para outro tipo de dado que prefira usar.

Existem alguns pacotes como o dotenv que se propõem a trabalhar com essas variáveis de uma maneira mais estruturada, mas pra que você entenda como as coisas funcionam de forma nativa, nosso pequeno exemplo já foi suficiente. Claro que você pode criar sua abstração e o que for necessário para lidar com suas variáveis de ambiente também.

#E como diferenciar esses valores no ciclo de vida de uma aplicação?

Isso pode mudar bastante dependendo de sua configuração e dos padrões que seu time usa, mas se pensarmos novamente nas 3 etapas do ciclo de desenvolvimento que pensamos, podemos imaginar algumas possíveis soluções.

#Variáveis de ambiente em tempo desenvolvimento

Se sua aplicação é bem simples, talvez apenas configurar alguns scripts para que forneçam valores diferentes, já pode ser o suficiente. Em um projeto JavaScript, você pode criar diferentes scripts para seus ambientes.

Poderíamos, de forma simples, separar nosso ambiente de desenvolvimento de um de produção, com os seguintes scripts (no seu arquivo package.json):

"scripts": {
	"start:dev": "VARIAVEL_DE_CONFIGURACAO=valor-desenvolvimento node .",
	"start:prod": "VARIAVEL_DE_CONFIGURACAO=valor-desenvolvimento node ."
}

Caso essas variáveis cresçam muito

#Variáveis de ambiente em tempo de compilação

Comentamos que o tempo de compilação pode não existir em algumas aplicações em Node. No entanto, é muito provável que você use Docker para criar um container e testar ou executar sua aplicação em um ambiente em nuvem.

Docker, por si só, é um assunto totalmente separado e extenso, mas vamos imaginar que, no caso da nossa aplicação em JavaScript, vamos utilizá-lo para gerar um “pacote” de um projeto pronto pra ser executado. Ou seja, ele será nosso “tempo de compilação”.

Você consegue fornecer esses argumentos ao realizar o build da sua imagem do docker através da instrução ARG.

Além disso, você pode definir as variáveis de ambiente que serão acessadas em tempo de execução através da instrução ENV também.

# essa variável será fornecida ao realizar o build da imagem do Docker
ARG VARIAVEL_DE_BUILD

# essa variável será fornecida ao executar a aplicação
ENV VARIAVEL_DE_EXECUCAO

# podemos até definir seu valor
ENV VARIAVEL_DE_EXECUCAO=valor

# podemos até reusar outra variável fornecida por ARG como variável de ambiente
ENV VARIAVEL_DE_EXECUCAO=${VARIAVEL_DE_BUILD}
#Variáveis de ambiente em tempo de execução

Todas as “técnicas” que comentamos aqui são responsáveis por tornar uma variável de ambiente acessível pra sua aplicação quando estiver rodando.

Além de usar qualquer uma delas (ou qualquer outra, até mesmo sua própria), geralmente você pode configurar o processo que irá iniciar sua aplicação pra fornecer alguns valores extras.

Nesse cenário, você consegue até desatrelar algumas variáveis da sua aplicação em si, configurando no seu provedor de nuvem, qual variável será adicionada no momento que sua aplicação for executada.

#O que fornecer em tempo de desenvolvimento, compilação e execução?

Como boa parte das respostas pra problemas computacionais: depende!

Depende do seu caso, da sua aplicação de do que cada variável vai fazer no seu código.

Voltando aos nossos dois cenários iniciais: mudar uma configuração de banco de dados e mudar a construção de URLs em uma aplicação, podemos imaginar duas soluções.

No primeiro caso, para mudar uma configuração de um banco de dados, talvez você configure essa credencial mais sensível em algum outro serviço e deixe-a mais distante do seu código fonte que está sendo versionado. Nesse caso, talvez utilizar algo com uma variável em tempo de execução, pode ser interessante.

Já no segundo caso, para mudar a construção de URLs, caso esses endereços de imagens sejam estáticos, talvez algo mais simples no momento de construir (compilar) sua aplicação já seja o suficiente. Até mesmo em uma configuração de Webpack ou algum bundler do framework que você possa estar usando.

Um outro exemplo comum é ter uma aplicação e queira precise usar o hash, a data do último commit realizado ou até mesmo uma tag, pra ter uma forma fácil de descobrir qual versão está rodando atualmente em algum ambiente de produção, por exemplo. Nesse cenário, talvez fornecer essa variável no momento em que sua aplicação está sendo preparada, antes de executá-la, pode ser interessante, já que você pode nem ter essas informações na hora de executá-la.

#E você, qual estratégia usa pra lidar com variáveis de ambiente?

Lembrando que tudo que comentei aqui (mesmo que um pouco genérico), tem o intuito de pintar algumas ideias e cenários de onde, como e quando usar variáveis de ambiente.

Claro que, nada disso exclui outras possibilidades de configuração ou de acesso aos valores (sensíveis ou não) de sua aplicação. Cada empresa, time e projeto pode usar algumas dessas e diversas outras estratégias que forem mais convenientes.


Compartilhe