Fale com a gente no WhatsApp Fale com a gente no WhatsApp
Fale com a gente no WhatsApp

Engenharia de Software

O que são os 12 factor app? - Parte 3

Nesta terceira parte, iremos abordar mais três dos 12 princípios: port binding, concurrency e disposability.

há 3 anos 8 meses

Formação Engenheiro de Software
Conheça a formação em detalhes

Nos artigos anteriores, começamos a compreender o que são os 12 factor app. Trata-se de uma série de princípios estabelecidos por uma equipe de desenvolvedores da Heroku para nortear o desenvolvimento de aplicações SaaS resilientes. Na primeira parte, vimos a definição do que são os 12 fatores, bem como analisamos três dos doze princípios: codebase, dependencies e configs.

Na segunda parte, vimos mais três dos 12 factor app: backing services, build, release and run; e processes. Nesta terceira parte, iremos abordar mais 3 dos 12 princípios: port binding, concurrency e disposability.

Port Binding

Quando desenvolvemos aplicações web, é comum que estas sejam executadas em ambientes isolados, chamados servidores de aplicação. Por exemplo, aplicações PHP podem ser executadas em servidores PHP, aplicações Java podem ser executadas no Tomcat ou Netty; e aplicações .NET podem ser executadas dentro do Kestrel ou do IIS.

O princípio do port binding diz que aplicações web não devem depender diretamente destes ambientes de execução, seja com relação a dependências ou mesmo mecanismos em tempo de execução para exposição da aplicação para a interface web. Isso quer dizer que uma aplicação Java, por exemplo, tem que ser capaz de ser executada e exposta na web sendo implantada tanto no Tomcat como no Netty, sem que sejam necessárias modificações na aplicação. Ao invés de a aplicação depender explicitamente de recursos do servidor web, ela deve escutar requisições HTTP através de uma porta HTTP, garantindo que todas as requisições em uma determinada porta sejam encaminhadas à ela.

Resumidamente, uma porta HTTP, que pode variar de 0 a 65535, é um possível caminho (ou endpoint) para enviar requisições para um servidor dentro de um protocolo de transporte. Existem alguns protocolos que já são associados à algumas portas por padrão de mercado. Por exemplo, é comum que fluxos de comunicação FTP ocorram através da porta 21. Já fluxos de comunicação de servidores de e-mail através de SMTP utilizem a porta 25. Por fim, também é muito comum que servidores HTTP (como Tomcat, Netty, IIS e Apache) utilizem a porta 80 para receberem requisições HTTP ou 443 para HTTPS.

Segundo o 12 factor app, uma aplicação tem que ser acessível através de uma interface HTTP definida por uma porta, que deve ser única e exclusiva para aquela aplicação. Essa deve ser a única maneira para expor uma aplicação para a web. Isso faz com que sejam necessários acoplamentos entre as aplicações e algum servidor web específico, além de garantir que os padrões de mercado sejam seguidos.

Concurrency

Quando as aplicações web são iniciadas em um servidor, naturalmente são definidas através de processos que são executados dentro do sistema operacional. Servidores web modernos, visando a melhor utilização de recursos das máquinas, têm apresentado outras maneiras para exporem aplicações web além de múltiplos processos. É comum que uma aplicação web se divida em processos-filhos do processo do servidor de aplicação, e estes sub-processos ainda podem ser divididos em mais unidades, como threads (caso de servidores como o Tomcat) ou até mesmo sub-rotinas (como as goroutines comuns no Go ou as coroutines comuns no Kotlin).

Servidores web ainda podem utilizar outras estratégias além destas, como o event looping comum no Node.js e no Netty Server. Por fim, isso ainda pode variar até mesmo com a linguagem e framework que é utilizado. Aplicações PHP geralmente são executadas como sub-processos do Apache, onde o servidor vai criando cada vez mais sub-processos à medida que a demanda da aplicação cresce. Já aplicações Java com frameworks como Spring utilizam o caminho inverso: na inicialização da JVM, uma quantidade considerável de recursos da máquina é pré-alocada previamente, sendo que estes recursos são gerenciados através de um processo de gestão de threads concorrentes. Ou, se falamos de aplicações Java com abordagem mais reativa, como aplicações que utilizam Vert.x, Webflux ou Micronaut, uma quantidade pequena de recursos e threads é inicialmente alocada, com estas aplicações usufruindo do conceito de event looping e sub-rotinas de web servers como o Netty. Claro que cada uma das abordagens tem pontos positivos e negativos, mas o ponto principal aqui é que esse processo todo de gestão de alocação e manipulação de recursos é praticamente transparente para o desenvolvedor.

O 12 factor app, através do fator concorrência (concurrency) prega que na verdade estes processos concorrentes são first class citizens, ou cidadãos de primeira classe. Isso quer dizer que os desenvolvedores devem levar em consideração também estes processos de gestão de concorrência para desenvolver aplicações mais resilientes e escaláveis ainda, se aproveitando das diferenças de cada um dos modelos de gestão.

Por exemplo, o processo principal de uma aplicação web pode ser definido dentro de um processo nativamente web, mas tarefas de background podem ser definidas em processos adjacentes e segregados, talvez até mesmo com um método de gestão diferente de um servidor web, desde que esse seja mais adequado. Além disso, estes modelos de gestão têm que estar preparados até mesmo para serem executados em ambientes fisicamente apartados e concorrentes entre si.

Isso quer dizer que uma aplicação deve conseguir ser capaz de escalar internamente dentro de um servidor web através de expansão dos processos de gestão de concorrências, como por exemplo, através de abertura de mais threads ou sub-processos e, se necessário, até mesmo suportar a execução em máquinas diferentes de maneira simultânea, tendo dois processos “mãe” rodando em ambientes diferentes com a mesma aplicação de maneira simultânea.

Atualmente, é comum lidarmos com os princípios de escalabilidade em máquinas diferentes através de ferramentas como o Docker, para garantir a exposição de aplicações em ambientes diferentes de maneira natural com o Kubernetes, auxiliando a fazer o processo de distribuição de carga entre os diferentes ambientes ou processos em execução simultâneos. Essa tem sido a linha adotada pelo mercado atualmente.

Porém, as aplicações em si também precisam ser escritas e arquitetadas de maneira que utilizem efetivamente os recursos das máquinas, seja através de uma distribuição inteligente de sub-processos, múltiplas threads ou estratégias como event looping. No fim, o grande foco do fator concurrency é garantir a escalabilidade e elasticidade das aplicações.

Disposability

Quando falamos dos 12 factor app, é importante lembrarmos que as aplicações, que são tratadas como processos, têm que ser capazes de serem encerradas da mesma maneira que têm que ser capazes de serem criadas, ou seja, de maneira natural e sem a produção de efeitos colaterais. Da mesma maneira que uma aplicação não pode apresentar comportamentos indesejados caso haja múltiplas instâncias sendo executadas simultaneamente, o encerramento de um dos processos também não pode causar efeitos adversos. E mais: estes processos têm que ter a capacidade de serem iniciados e encerrados de maneira muito rápida. Estas características tornam a aplicação mais elástica e escalável ainda, tendo em vista que a aplicação, em questão de segundos, tem que ser capaz de iniciar ou encerrar processos concorrentes de acordo com a demanda. Estas são as principais diretivas do princípio disposability.

Disposability tem uma relação muito interessante com concurrency. Devemos ter aplicações que têm que ser capazes de serem executadas em múltiplos ambientes de maneira concorrente. Mas, um dos sub-processos de uma aplicação, como uma instância, pode falhar ao ser criado por uma série de razões (como falta de recursos do sistema operacional ou falha ao acessar serviços como bancos de dados).

Quando um desses processos falha (lembrando que aqui estamos considerando como sendo uma instância da aplicação), essa instância problemática precisa ser encerrada o mais rapidamente possível, pois ela não é mais operacional. Ao mesmo tempo, uma outra instância precisa ser criada para substituir a instância defeituosa. E tudo isso é necessário para que a experiência dos usuários destes processos não seja prejudicada: eles não podem perceber estas possíveis falhas.

Aqui entra o princípio de disposability. A instância defeituosa tem que ser capaz de se encerrar rapidamente sem provocar efeitos colaterais nas demais instâncias, ao mesmo tempo que a instância substituta precisa ser iniciada o mais rápido possível para evitar a sobrecarga das demais instâncias operacionais. Com isso, podemos ver que o princípio de disposability também tem uma relação muito forte com o aspecto de escalabilidade de uma aplicação.

Existem outros aspectos envolvidos com o fato de uma aplicação poder ser encerrada rapidamente. Se um processo que será encerrado estava no meio de um processamento, é altamente provável que o consumidor da aplicação irá repetir o comando. Até mesmo a repetição do comando não pode causar efeitos colaterais na aplicação: a mesma tem que processar o comando “repetido” de maneira completamente transparente. Isso é o que normalmente chamamos de idempotência, se tornando uma decorrência dos princípios de concurrency e disposability.

Autor(a) do artigo

Marylene Guedes
Marylene Guedes

Responsável pelo sucesso do cliente na TreinaWeb. Graduada em Gestão de Tecnologia da Informação pela FATEC Guaratinguetá, além de estudante de UX/UI.

Todos os artigos

Artigos relacionados Ver todos