1 pontos por GN⁺ 2023-10-13 | 1 comentários | Compartilhar no WhatsApp
  • The Twelve-Factor App é uma metodologia para operar e escalar apps web·SaaS no longo prazo, abordando junto automação de configuração, portabilidade, deploy em nuvem e entrega contínua
  • Não fica preso a uma combinação específica de linguagem de programação ou de serviços de apoio como banco de dados, fila ou cache em memória, podendo ser aplicado a vários tipos de aplicações como serviço
  • Baseia-se na experiência de participação direta no desenvolvimento e deploy de centenas de apps na plataforma Heroku, além da observação indireta do desenvolvimento, operação e expansão de centenas de milhares de apps
  • A principal preocupação é oferecer um vocabulário comum para reduzir o custo de colaboração e a erosão de software que surgem quando um app cresce organicamente
  • Pode ser usado como referência prática não só por desenvolvedores que criam aplicações de serviço, mas também por engenheiros de operações responsáveis por implantá-las e gerenciá-las

12 princípios operacionais para apps SaaS

  • O software moderno costuma ser oferecido na forma de app web ou SaaS, e o Twelve-Factor App é uma metodologia para criar esse tipo de aplicação
  • O objetivo é tornar mais previsíveis os processos de desenvolvimento, deploy e operação do app
    • Usa automação declarativa de configuração para reduzir o tempo e o custo de integrar novos desenvolvedores ao projeto
    • Define um contrato claro com o sistema operacional subjacente para aumentar a portabilidade entre ambientes de execução
    • É projetado para reduzir a carga de administração de servidores e sistemas e se adequar ao deploy em plataformas modernas de nuvem
    • Reduz a diferença entre ambiente de desenvolvimento e produção para viabilizar entrega contínua
    • Permite escalar sem mudar drasticamente ferramentas, arquitetura ou práticas de desenvolvimento
  • O escopo de aplicação não se limita a uma stack tecnológica específica
    • Pode ser aplicado a apps escritos em qualquer linguagem de programação
    • Serviços de apoio incluem bancos de dados, filas, caches em memória etc.

Contexto organizado a partir da experiência na Heroku

  • Os contribuidores participaram diretamente do desenvolvimento e deploy de centenas de apps na plataforma Heroku e observaram indiretamente o desenvolvimento, a operação e a expansão de centenas de milhares de apps
  • Com base na experiência e nas observações obtidas em apps SaaS reais, organizaram práticas ideais para o desenvolvimento de aplicações
    • Prestaram atenção ao fluxo em que o app cresce organicamente ao longo do tempo
    • Tratam da forma como vários desenvolvedores colaboram na mesma codebase
    • Consideram evitar o custo da erosão de software um objetivo importante
  • O formato foi inspirado em Patterns of Enterprise Application Architecture e Refactoring, de Martin Fowler

Os 12 fatores

1 comentários

 
GN⁺ 2023-10-13
Opiniões no Hacker News
  • O 12-Factor App é uma recomendação criada em 2011 mais apoiada no Heroku e nas limitações da infraestrutura conteinerizada da época, não um documento que pareça profundamente baseado em princípios de engenharia.
    Por exemplo, a afirmação de que se deve colocar configuração em variáveis de ambiente existe porque os autores trabalhavam na Heroku, e a Heroku preenchia variáveis de ambiente por meio dos campos de entrada das aplicações web.
    Se você quer rastrear o histórico de configuração com controle de versão, usar GitOps, usar ConfigMap do k8s ou colocar arquivos de configuração em um volume montado, todas essas são, em geral, escolhas aceitáveis. Porque separam o estado da configuração do estado de implantação da aplicação.
    Vejo esse documento como uma diretriz prejudicial, porque confunde a floresta com as árvores e faz recomendações alinhadas aos recursos do produto da empresa que o escreveu, mais do que a princípios reais de engenharia.

    • É verdade que ele foi influenciado em certa medida pelo funcionamento da Heroku, mas ConfigMap e GitOps não atendem da mesma forma aos requisitos de segurança e usabilidade que a configuração/variáveis de ambiente da Heroku atendem.
      Se você quer armazenamento seguro de configuração no Kubernetes, acaba usando Secrets, e isso assume a forma de chave-valor, como variáveis de ambiente. Para obter segurança semelhante no Git, é necessária uma camada de criptografia, o que quebra diffs e exige ferramentas adicionais.
      No fim, voltamos ao motivo pelo qual ferramentas de implantação de alto nível como a Heroku foram criadas.
    • Hoje ninguém mais chama isso de 12 factor, mas graças a ele ainda seguimos princípios gerais. O 12-Factor era um documento anterior à popularização de Docker e Kubernetes.
      O princípio de tratar logs como streams continua correto. Basta escrever logs no STDOUT, não em arquivos, e deixar o orquestrador ler e armazená-los.
      A configuração vem do ambiente. Dependendo da forma de implantação, aplicações tendem a ler configurações de fontes diferentes: localmente de um .env, em produção de um armazenamento de segredos.
      O port binding também é igual: a aplicação abre uma porta, e algo como nginx fica na frente para compor um proxy reverso. Serviços e ingress do K8S cumprem esse papel.
      Hoje, a maior crítica que se pode fazer ao 12-Factor é que o documento não foi realmente bem escrito e presume que o leitor já sabe exatamente do que ele está falando.
    • A Heroku não inventou as variáveis de ambiente, nem foi a primeira a ter a ideia de usá-las para configuração de aplicações. Era uma prática usada havia muito tempo antes da Heroku.
      Ainda assim, reconheço o mérito da Heroku por popularizar esse conceito e ampliar seu uso.
    • “Armazene configuração no ambiente” não significa necessariamente colocar a configuração em variáveis de ambiente. Significa que a configuração vem do ambiente de hospedagem, não da própria aplicação.
      Não é adicionar um settings.json à máquina antes de iniciar a aplicação; é o mesmo código-fonte usar os valores configurados naquele cluster quando implantado no cluster AKS do Azure EU north, e usar a configuração daquele cluster quando implantado no cluster Docker Swarm em um RPi Zero dentro de uma moldura da Ikea.
      O nome desse cluster é Gibson.
    • Aplicações 12-Factor que colocavam configuração em variáveis de ambiente eram triviais de mover para ConfigMap. Por outro lado, aplicações corporativas atuais com layouts complexos de ConfigMap — no pior caso, dependendo diretamente da API do k8s — serão um inferno para migrar para o que vier depois do k8s.
  • Sinto que cada um desses itens pode ser refutado de forma bastante razoável.
    Primeiro, o princípio de um repositório para uma aplicação não é fundamentalmente errado. Não há problema em desenvolver em um único repositório várias aplicações que são fortemente acopladas funcionalmente e compartilham o ciclo de release, mas que precisam ser implantadas separadamente para obter as vantagens de processos separados e escalabilidade independente. Penso na separação entre API pública e processos worker, por exemplo Sidekiq em Ruby, Celery em Python ou consumidores Kafka comuns.
    Segundo, dizer que “uma aplicação 12-Factor não depende da existência implícita de pacotes globais do sistema” é muito difícil de alcançar na prática sem usar algo como Nix. Dependências da API de chamadas de sistema do kernel também vazam, e a maioria das aplicações Rust depende implicitamente da glibc, exceto quando usa musl. Ela existe nas principais distribuições Linux, salvo distribuições slim como Alpine. Vejo Docker como um apoio necessário para mitigar esse problema.
    Terceiro, armazenar configuração em variáveis de ambiente parece mais frágil. Pode reduzir a segurança ao forçar segredos a ficarem no ambiente, e faz abrir mão de configuração estruturada em arquivos, que oferece segurança de tipos, autocompletar na IDE e parsing automático. Em variáveis de ambiente, é preciso implementar por conta própria parsers para valores de configuração complexos que não sejam strings. Na prática, muitas vezes isso acaba sendo salvo em arquivos .env e comitado no repositório, então o argumento de segurança de commit também perde sentido.

    • Isso é uma interpretação errada da diretriz. O texto original fala de bibliotecas de suporte instaladas por sistemas de empacotamento específicos de linguagem, como CPAN ou Rubygems, e explica que essas bibliotecas podem ser instaladas como “site packages” globais do sistema.
      Não quer dizer que você não deva depender do sistema operacional, incluindo glibc; quer dizer que você não deve fazer com que a aplicação só funcione se um pacote de um gerenciador específico de linguagem estiver instalado na máquina.
      Concordo com os demais pontos. Especialmente a parte sobre variáveis de ambiente parece menos um conselho com base real e mais algo que os autores escreveram assumindo como melhor prática uma abordagem comum no desenvolvimento Ruby da época.
    • Se são implantadas separadamente, elas não compartilham o ciclo de release. Em algum momento, uma combinação de versões incompatíveis pode acabar em execução. Por exemplo, apenas uma parte pode falhar na implantação.
      Portanto, é preciso se preparar para essa situação, e acho que repositórios separados são melhores para testar facilmente combinações de versões diferentes.
      A maioria das linguagens de alto nível não depende de uma glibc específica. Se o runtime da linguagem funciona corretamente, a aplicação também funciona sobre ele. Claro que, em alguns casos, você acaba usando algo como Docker. O fato de ser difícil não significa que não tenha valor.
    • Se essa é a parte fundamental, não concordo. Acho que uma parcela maior do mundo concorda comigo e com os princípios 12-Factor.
    • Concordo com a perspectiva sobre a primeira regra e colocaria tranquilamente várias aplicações relacionadas em um único repositório. Como não é apresentada uma justificativa para que um repositório deva conter apenas uma aplicação, essa regra pode ser ignorada.
      Linguagens dinâmicas ou gerenciadas muitas vezes conseguem ignorar a maior parte das complicações relacionadas a pacotes do sistema.
    • A utilidade de um monorepo é em forma de U. A maioria dos projetos fica em algum ponto no meio, especialmente projetos mantidos por equipes de engenharia independentes que não têm uma equipe centralizada de plataforma/DevOps/seja lá como chamam isso hoje.
  • No geral, gosto da ideia, mas houve tantas vezes em que pessoas não técnicas ou meio técnicas sacaram o “12 factor” como se fosse um cartão amarelo universal para atrasar releases que acabei passando a ignorá-lo quase completamente
    Na verdade, com “agile” foi parecido. Entendo a intenção dessas diretrizes, mas o valor prático delas parece ser muito maior para pessoas que só conseguem oferecer liderança técnica de torre de marfim

    • Ignorar completamente por princípio é tão ruim quanto tratar isso como uma doutrina obrigatória
      Já passei por casos em que engenheiros juniores excessivamente empolgados ou aspirantes a arquitetos usavam o texto do 12-Factor como se fosse requisito obrigatório para todo lançamento
      Essas coisas são bons objetivos a perseguir, mas é preciso explicar de forma firme e consistente que, no mundo real, é necessário fazer concessões para lançar e escolher o que será adiado ou postergado
    • Chamamos isso de doutrina armada. Ainda assim, acho melhor do que gente que insiste em DRY criando funções helper genéricas incompreensíveis só para reduzir 3 ou 4 linhas de duplicação de código
    • É preciso dar exemplos. Como a maioria das coisas, depende do contexto e há zonas cinzentas, mas acho que a maioria dos desenvolvedores usa o 12-Factor como uma estrela-guia
      Um desvio pequeno não deveria bloquear um release, mas, se no geral não estiver alinhado, no mínimo deve ser tratado como dívida técnica. Se um release representa um grande retrocesso em um dos itens, é justo bloqueá-lo ou, pelo menos, exigir uma revisão mais detalhada de por que aquela concessão foi considerada válida
    • Nenhuma doutrina deve impedir uma implantação dentro do escopo de um MVP. Depois que se sai do MVP, tudo que não foi resolvido do ponto de vista de boas práticas vira dívida técnica
      Se a organização adotou o 12FA como boa prática, ele deve ser atendido, mas não deve impedir a implantação
      12FA não é uma única caixa a marcar. À medida que o produto amadurece, cada item pode — e na maioria das vezes deve — ser implementado adicionando-o ao produto um por um, subdividindo quando necessário
      Se a engenharia inicial foi bem feita, isto é, com abstrações e interfaces adequadas e sem deixar tudo hardcoded, isso não deveria ser um problema
      YAGNI é tão abusado quanto 12FA. O que mais falta no 12FA são exemplos concretos que engenheiros juniores possam consultar
    • Já tive esse problema antes, e a solução foi ter um processo claramente documentado e apontar para ele
      Aprendi a encarar esse tipo de problema de boa-fé. É preciso ver por que a questão está sendo levantada, se o processo existente é pouco claro, defeituoso ou pouco confiável
      Se uma preocupação razoável for encontrada, basta mudar o processo e atualizar a documentação
  • O Twelve-Factor App diz para usar o ambiente para configuração, e o Docker diz para não usar o ambiente para configuração, porque não é seguro
    Gosto e uso muitos dos padrões do 12-Factor, mas alguns foram pensados no contexto de VPS. Naquela época, o ambiente era estável, seguro e mais fixo; em contêineres, porém, o ambiente pode acabar sendo carregado em alguma camada
    Na era dos contêineres, esse item específico foi uma preocupação considerável. Docker secrets nem sempre se encaixa bem, então é preciso fazer várias acrobacias para funcionar

    • Você pode explicar em mais detalhes? Fico curioso sobre como variáveis de ambiente são inseguras. Se um segredo precisa existir durante toda a vida útil da aplicação, não sei qual seria uma alternativa melhor
      Injetar segredos na aplicação como variáveis de ambiente não significa que todo o tratamento externo a isso seja inseguro. Por exemplo, contêineres do AWS ECS têm suporte integrado para buscar segredos no Secret Manager na inicialização e passá-los como variáveis de ambiente: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/...
      O segredo é obtido no momento em que o contêiner inicia, usando as credenciais IAM da aplicação em execução para buscá-lo no Secret Manager. Portanto, é necessário ter permissão para esse segredo
      A vantagem de usar variáveis de ambiente parece depender totalmente de como elas acabam sendo configuradas. Com um mecanismo assim, não vejo grandes desvantagens
      A principal desvantagem que vejo é que um malware genérico tentando despejar variáveis de ambiente poderia capturar os valores, mas, se os segredos não deixam de ficar armazenados permanentemente na memória, bloquear essa ameaça em geral se aproxima mais de ofuscação
    • Por outro lado, sem suporte da plataforma, não há muitas alternativas fáceis. No mínimo, isso incentiva a separação entre configuração e código e permite manter segredos fora do sistema de controle de versão
    • Por esse motivo, nunca gostei de colocar segredos em variáveis de ambiente. Consoles de debug malfeitos expuseram variáveis de ambiente por acidente com uma frequência surpreendente
      É algo que você realmente quer evitar quando se trata de uma chave privada
      Não vejo grandes problemas em montar segredos no sistema de arquivos com k8s. Claro que tudo isso depende do ambiente de implantação
      Às vezes, variáveis de ambiente são a opção menos ruim. Segredos são sempre difíceis por natureza
    • É possível argumentar que nenhuma entrada deve ser considerada “segura para consumir”. Se você escreve o código da aplicação de forma defensiva, nunca assume que ENV é seguro ou tem o formato esperado, e o sanitiza antes de usar
      Se você confiar cegamente na entrada só porque é single-tenant, quando as condições mudarem depois por alguma guinada arbitrária de negócios, acabará com bugs difíceis de rastrear e longas noites
    • Em retrospecto, o nome daquela seção teria sido melhor como separação da configuração do código, como aparece no primeiro parágrafo
  • Nos últimos anos, discuti bastante sobre apps 12-Factor e também vi muita confusão. O site do 12-Factor é excelente, mas é útil principalmente para quem já entende por que aqueles itens são importantes.
    Para quem não sabe o motivo por trás das regras, era necessária uma explicação mais aprofundada. Por isso fiz o vídeo “What are 12 Factor Apps and Why Should You Care?”[1], e ouvi que ele ajudou bastante em algumas empresas que o usam no treinamento de novos engenheiros/DevOps
    Independentemente de onde você aprenda, vale dedicar uma ou duas horas para estudar apps 12-Factor. A maioria das “regras” são coisas que exigem consciência, e não ficam imediatamente claras até você errar por conta própria e sofrer com isso
    [1] https://youtu.be/REbM4BDeua0

    • É parecido com testes unitários. A ideia de escrever código que testa código parecia tão óbvia desde que comecei a programar que fiquei surpreso com quanto tempo outras pessoas levaram para aceitá-la.
      O conceito de “teste unitário” começou a ganhar tração por volta do fim dos anos 90, e foi um alívio ver alguém com autoridade defendê-lo.
      O motivo de eu não fazer isso de fato até Kent Beck lançar o JUnit era que o código em que eu trabalhava não tinha uma estrutura boa para ser controlado por outro código. Variáveis globais, estado espalhado, dependência de sistemas externos e de um layout específico de sistema de arquivos, além de ignorar modularidade, faziam com que nada pudesse ser executado fora do contexto para o qual tinha sido projetado.
      Tudo isso era “mau design”, mas os prazos eram cumpridos, então todo mundo fazia assim. Eu esperava que, quando os testes unitários ganhassem tração, os programadores se afastassem dos designs monolíticos.
      Depois de 25 anos vendo testes unitários para getters/setters e um único teste unitário gigantesco que cria um banco de dados em memória porque todas as funções do app precisam de um banco de dados real só para tentar executar, para no fim falhar e ser comentado, perdi a fé de que testes unitários se tornariam algo além de uma caixa de seleção sem sentido. Todo mundo marca porque é uma “boa prática”, mas não para para pensar no porquê.
  • O conselho sobre configuração sempre foi o ponto de que eu mais discordei. Configuração acaba sendo definida por várias partes, muitas vezes também por desenvolvedores, então frequentemente o melhor é empacotar valores padrão razoáveis junto com a aplicação e sobrescrevê-los com arquivos por ambiente e variáveis de ambiente.
    Para a maioria das aplicações server-side, esse é o método mais flexível. Muitas vezes você já sabe qual deve ser a configuração, e é melhor que isso fique sob controle de código-fonte, mas valores secretos devem ser injetados em tempo de execução.
    Para não gastar um tempo enorme de configuração em desenvolvimento, teste e produção, a configuração precisa de sobrescrita hierárquica.

    • O problema é se “valores padrão razoáveis” são padrões de desenvolvimento ou padrões de produção.
      Se forem de desenvolvimento, acabarão quebrando produção; e padrões de produção podem não fazer sentido nenhum em desenvolvimento.
    • No contexto em que o 12-Factor App foi escrito, havia a ideia de poder alternar modos com variáveis de ambiente como RAILS_ENV=test.
      Uma forma de pensar na seção de configuração é: “essa estratégia de configuração combina bem com contêineres?”. Ao construir uma imagem, você cria um estado de disco estático no qual mudanças não persistem a menos que uma nova imagem seja criada.
      Se a configuração for apenas baseada em arquivos, é preciso construir uma imagem completamente nova para alternar entre o comportamento de teste e o de produção.
      Permitir que a configuração seja alterada independentemente do disco base ajuda a isolar mudanças. É preciso distinguir se o app quebrou porque o deploy, isto é, a criação da imagem, quebrou, ou porque a configuração estava errada.
      Ao separar a criação da imagem da mudança de configuração, essa própria pergunta desaparece.
    • Se você impede sistematicamente que uma instância de QA se conecte a recursos de produção, talvez não haja problema em implantar a maior parte da configuração junto com o app.
      Por exemplo, não se deve permitir um erro humano bastante previsível como enviar, a partir de QA, 100 mil novas tentativas de negação de autenticação para uma fila de submissão de jobs de produção.
      Ainda assim, muitas configurações não dizem respeito à infraestrutura, mas a coisas como qual bean ResolverStrategy conectar em cada ambiente.
    • Você pode, e provavelmente deve, ter valores padrão razoáveis.
      A configuração deve ser versionada, mas gerenciada separadamente do código-fonte. Isso porque a configuração descreve o deploy em si, não a imagem usada no deploy.
    • Não vejo motivo para que valores padrão também não possam ser expressos como configuração. Se era isso que se queria dizer, não considero que contrarie o 12-Factor.
  • Certamente foi uma norma de engenharia influente. Hoje há muitas abstrações fáceis de hospedagem, como Render ou Vercel, então é um pouco estranho pensar que este documento foi escrito em 2012, quando apps web eram muito mais como um Velho Oeste em termos de práticas comuns aceitas.

  • O que falta muito neste documento é a justificativa para as regras. Quase tudo é apenas regra.
    É difícil avaliar se as regras são boas, e o documento não ajuda a descobrir isso.

  • Só pelo título, achei que fosse um comentário sobre autenticação em duas etapas. Tipo aqueles apps que exigem foto do passaporte, escaneamento facial, carteira de motorista, SMS, Google Authenticator, link por e-mail, senha e impressão digital só para fazer login uma vez.

    • Acho que uma exchange de criptomoedas centralizada média é assim.
    • É mais ou menos o que uma corretora média de ações ou de criptomoedas exige antes de permitir negociar.
  • Nos primórdios do Docker, fiz bastante trabalho para fazer o WordPress se comportar como um Twelve-Factor App.
    Tradicionalmente, o WordPress não se comportava assim, e isso até fazia sentido. O WordPress cresceu em um mundo em que servidores longevos com disco local gravável e persistente eram comuns.
    As coisas devem ter mudado bastante desde então. Estou falando de algo por volta de 2016, mas foi um desafio muito interessante.

    • Lembro quando aprendi a tornar servidores sem estado, por exemplo armazenando informações de sessão no banco de dados e não escrevendo no disco.
      Fiquei surpreso com o quanto as coisas ficavam mais simples, e passou a ser possível balancear carga entre vários nós sem se preocupar com afinidade de sessão.
      Claro, isso ficou mais difícil de outras formas, como a necessidade de um banco de dados separado para sessões.