|
| 1 | +Title: Explicit is better than implicit |
| 2 | +Date: 2016-04-22 23:00 |
| 3 | +Tags: python, zen of python, experências, decisões de desenvolvimento |
| 4 | +Category: Experiências |
| 5 | +Slug: explicit-is-better-than-implicit |
| 6 | +Author: Ivan Neto |
| 7 | +About_author: Desenvolvedor Python, esposo, pai, escritor nas horas vagas |
| 8 | +Email: ivan.cr.neto@gmail.com |
| 9 | +Github: ivancrneto |
| 10 | +Twitter: ivancrneto |
| 11 | +Linkedin: ivanrocha |
| 12 | + |
| 13 | +Esse post não é diretamente relacionado a desenvolvimento com Python, mas conta a história de uma das muitas experiências que passamos desenvolvendo e mostra como a filosofia e o _mindset_ __Python__ podem nos influenciar a tomar decisões melhores. |
| 14 | + |
| 15 | +## Contexto geral |
| 16 | + |
| 17 | +Atualmente trabalho remotamente pela Toptal, uma empresa de consultoria em _software_ com foco em trabalho remoto e que tem um processo seletivo bastante rígido para garantir uma qualidade acima da média para seus clientes ([saiba mais sobre a Toptal aqui](https://www.toptal.com/#book-tested-programmers)). |
| 18 | + |
| 19 | +No time em que faço parte os papéis são bem definidos entre desenvolvedores _front-end_ e _back-end_ e faço parte da equipe de _back-end_, que usa principalmente __Django__ nas aplicações. À medida que evoluímos e nos tornamos mais maduros como time, buscamos soluções que pudessem otimizar nosso processo de desenvolvimento. |
| 20 | + |
| 21 | +Atualmente utilizamos _CircleCI_ -- uma plataforma para integração e entrega contínuas -- para tarefas como rodar nossa suíte de testes, fazer a integração de nosso código, instanciar uma nova versão de nossos sistemas em um ambiente de _staging_ e criar imagens __Docker__ posteriormente colocadas em produção. |
| 22 | + |
| 23 | +## Melhorias |
| 24 | + |
| 25 | +Nosso time constantemente reavalia processos, ferramentas e o resultado são discussões interessantes sobre como tornar nosso trabalho mais rápido e produtivo. |
| 26 | + |
| 27 | +Recentemente começamos a utilizar um servidor __NPM__ -- um dos mais usados gerenciadores de pacotes para __Javascript__ -- privado para uma melhor separação de pacotes _front-end_, otimizando o tempo de _build_ de _assets_ de 47 para 25 segundos. |
| 28 | + |
| 29 | +Na raiz do nosso projeto temos um _package.json_ com o seguinte conteúdo: |
| 30 | + |
| 31 | +``` json |
| 32 | +{ |
| 33 | + // [ ... ] |
| 34 | + "dependencies": { |
| 35 | + "cat": "^1.0.0", |
| 36 | + "front": "^1.0.0", |
| 37 | + "core": "^1.0.0", |
| 38 | + }, |
| 39 | + // [ ... ] |
| 40 | +} |
| 41 | +``` |
| 42 | + |
| 43 | +Sendo que __cat__, __front__ e __core__ (renomeados para exemplificar) são pacotes mantidos por nós mesmos no __NPM__ privado. Por padrão, se você lista o pacote com `“^”` (como por exemplo acima `“^1.0.0”`), o _npm_ considera apenas o número que representa a _major version_, no caso o número 1, e fará o _download_ da última versão que começa com 1. |
| 44 | + |
| 45 | +Essa abordagem tem quatro pontos fracos: |
| 46 | + |
| 47 | + 1. Ela pode quebrar seu código. Se pacote de terceiro atualizar, seu código pode não estar preparado para lidar com as novas funcionalidades adicionadas, principalmente porque _libs_ evoluem tão rapidamente que se torna fácil acontecer uma atualização sem _backwards compatibility_. |
| 48 | + 2. Você não sabe exatamente qual versão do pacote seu sistema está usando em produção. Para saber, você teria que acessar os servidores remotamente e executar o comando `npm list`, por exemplo (poderia fazer localmente também mas existe a possibilidade de que no momento em que ocorreu o _deploy_, aquele pacote estava em uma versão anterior à sua versão local). |
| 49 | + 3. Você perde o controle de quando quer que seu sistema utilize a nova versão do pacote. |
| 50 | + 4. Se você precisar fazer um _rollback_ ou usar uma imagem antiga de seu sistema em produção, ainda assim ela vai utilizar a última versão do pacote, o que pode levar a mais dores de cabeça. |
| 51 | + |
| 52 | +# Problema |
| 53 | + |
| 54 | +Recentemente tivemos um _bug_ em produção, e uma mudança no pacote _core_ resolveria. __O que fazer com o sistema principal?__ Nada, não era necessária nenhuma alteração. Só precisaríamos gerar uma nova imagem _Docker_ que ela seria montada do zero e no momento de instalar os pacotes _npm_, baixaria a última versão. |
| 55 | + |
| 56 | +Bastava realizar _rebuild_ na branch _master_ no __CircleCI__, que assim que terminado ele trataria de enviar um _webhook_ para o nossa ferramenta que cria imagens _Docker_. Nós utilizamos o seguinte padrão de nomenclatura dessas imagens: |
| 57 | +``` |
| 58 | +myapp-production-<branch>-<sha[:7]> |
| 59 | +``` |
| 60 | +Como não fizemos nenhuma alteração no sistema principal, o _branch_ e o _sha_ continuaram os mesmos. |
| 61 | + |
| 62 | +Resumindo, nosso _Docker_ recebeu um pedido de _build_ para aquela _branch_ e _sha_ e, por padrão, primeiro procurou em seu _cache_ de imagens se já existia alguma imagem pronta com aquele nome. O resultado foi que a mesma imagem, sem o _hotfix_, foi para produção (pois ela havia sido criada antes e no momento em que baixou os pacotes _npm_ ainda não havia alterações no _core_). |
| 63 | + |
| 64 | +Demoramos um pouco para perceber o problema, mas o suficiente para resolvê-lo sem que _stakeholders_ percebessem. |
| 65 | + |
| 66 | +## Solução |
| 67 | + |
| 68 | +Algum tempo depois discutimos e nós desenvolvedores _back-end_ sugerimos a seguinte solução: |
| 69 | + |
| 70 | +``` json |
| 71 | +{ |
| 72 | + // [ ... ] |
| 73 | + "dependencies": { |
| 74 | + "cat": "1.0.5", |
| 75 | + "front": "1.0.7", |
| 76 | + "core": "1.0.10", |
| 77 | + }, |
| 78 | + // [ ... ] |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +Com essa abordagem: |
| 83 | + |
| 84 | +1. Você pode fazer _rollback_ do seu código sem problemas pois o código antigo vai usar a versão antiga do pacote. |
| 85 | +2. Você tem controle sobre quando quer que seu sistema utilize a nova versão do pacote. |
| 86 | +3. Você sabe exatamente quais versões de pacotes seu sistema está utilizando, bastando abrir o _packages.json_. |
| 87 | +4. Caso uma nova versão quebre seu código, você pode voltar uma versão rapidamente até que o problema seja resolvido. |
| 88 | + |
| 89 | +O problema que tivemos em produção não aconteceria caso tivéssemos utilizado a abordagem acima. Assim que os pacotes fossem atualizados, criaríamos uma _pull request_ no repositório do sistema principal com as seguintes alterações: |
| 90 | + |
| 91 | +``` diff |
| 92 | +diff --git i/package.json w/package.json |
| 93 | +index eaae10d..5aa773b 100644 |
| 94 | +--- i/package.json |
| 95 | ++++ w/package.json |
| 96 | +@@ -9,7 +9,7 @@ |
| 97 | + "dependencies": { |
| 98 | + "cat": "1.0.5", |
| 99 | + "front": "1.0.7", |
| 100 | +- "core": "1.0.10", |
| 101 | ++ "core": "1.0.11", |
| 102 | + }, |
| 103 | +``` |
| 104 | +Após o _merge_, um novo _build_ aconteceria no __CircleCI__, e um novo _sha_ seria enviado via _webhook_. O _Docker_ não encontraria nenhuma imagem com essa combinação de _branch_ e _sha_ e criaria uma nova do zero. Produção teria o _hotfix_ e não haveria constrangimento. |
| 105 | + |
| 106 | +Os desenvolvedores _front-end_ não gostaram da ideia de ter que atualizar o arquivo toda vez que alguma dependência subisse de versão. Discutimos bastante e a última coisa que eu disse foi: __“from the Zen of Python: explicit is better than implicit”__. |
| 107 | + |
| 108 | +Lição aprendida. |
0 commit comments