Olá! Meu nome é Gildásio. Sou engenheiro de software da equipe de iFood para Parceiros, e estou aqui para falar sobre nossa jornada de três partes na reescrita, refatoração e redesign do app. O objetivo é deixar o app num estado onde todos se sintam confortáveis para adicionar novas funcionalidades enquanto gerenciamos as já existentes, sejam eles novos na equipe ou não.
Prólogo
Quando eu cheguei no iFood, em maio de 2021, a equipe estava numa situação difícil depois de perder um integrante – e tínhamos algumas entregas para fazer num curto espaço de tempo. Sabíamos que, em breve, teríamos ajuda externa por alguns meses, mas o app, como estava naquele momento, não estava amigável o suficiente para que uma nova equipe entrasse assim. Decidimos por uma nova arquitetura baseada no bloc para a UI, e arrumamos (mais ou menos) nossa arquitetura atual para o resto. Ainda assim, tínhamos um problema maior pela frente: estávamos usando o Flutter 1.22.6.
A situação era a seguinte: sprint atual/lançamentos para terminar, nova equipe e novas funcionalidades chegando, arquitetura ainda não organizada, plugins antigos e uma versão antiga do Flutter. Não podíamos esperar, considerando que a próxima atualização do Flutter (que anunciou o 2.2) estava pra sair, e isso significaria ainda mais trabalho se ainda não estivéssemos no 2.0.
Ao mesmo tempo, este foi o melhor momento para fazer a migração, considerando que estávamos numa pausa enquanto as próximas funcionalidades ficavam prontas para a etapa de desenvolvimento. Ou seja, o prazo para isso era de duas semanas.
Ficou decidido o seguinte:
– Separar um branch do desenvolvimento chamado develop_2.0 e iniciar a migração por lá, módulo a módulo;
– Continuar desenvolvendo as funcionalidades do sprint atual separadamente, para convertê-los depois, pois precisávamos ainda terminar uma versão;
– Escrever e detalhar uma nova arquitetura para a nova equipe, para já saberem o que precisávamos para o novo módulo, já usando o Flutter 2.0 desde o início.
Um deles definitivamente não deu certo. Imagina qual?
A migração
Tínhamos apenas duas semanas para migrar um aplicativo com 22 módulos antes que a próxima funcionalidade chegasse e, para facilitar as coisas, pensamos: “migrar cada módulo separadamente vai dar bom, né?”.
Não.
Um pouco do contexto: o aplicativo iFood para Parceiros começou há dois anos, com uma equipe que estava aprendendo Flutter enquanto construía o app. Isso nos deixou com padrões e uma arquitetura que tornaria nosso trabalho de migração um pouco difícil – um desses, justamente, relacionado a como os módulos estavam interligados. Não podemos migrar o módulo 1, por exemplo, porque ele depende do 3, 4 e 7. E esses, por sua vez, dependem de outros.
Portanto, a abordagem por módulos não estava funcionando. O que poderíamos fazer depois de garantir que os módulos sem dependências fossem migrados com segurança? Executar dart migrate em cada módulo de uma só vez, certo?
Calma, não fizemos isso – mas quase.
Primeiro, precisávamos ter certeza de que estávamos seguindo as etapas corretas. Felizmente, todos os pacotes que estávamos usando naquela época já tinham uma versão null-safety disponível no canal stable ou no beta.
Em seguida, mapeamos os módulos com menos dependências e começamos por eles, gradualmente para, aos poucos, tornar mais fácil também pegar os módulos maiores.
Aí, sim, executamos dart migrate! Foi o que tornou tudo muito mais fácil e seguro, (considerando que tínhamos apenas duas semanas). A ferramenta teve suas deficiências, mas definitivamente foi o que tornou a façanha possível em tão pouco tempo.
Discutimos um pouco se migraríamos para o 2.0 com o null-safety desativado ou não, e cada artigo e recomendação deixava apenas uma escolha: é melhor fazer isso agora, antes que seja obrigatório e que sobre ainda mais trabalho pra depois. Valeu a pena? Com certeza!
Vindo de Kotlin, senti falta do null-safety nos três anos em que trabalhei com o Flutter. Nos ajuda a nos tornar menos propensos a erros, e deixa os novos plugins e pacotes mais seguros, com recursos como a late initialization (que ainda não temos a versão async que o Kotlin permite). Você pode dar uma olhada na explicação oficial aqui.
Lições aprendidas:
- Ter uma ferramenta tão boa como o dart migrate ajuda na tarefa “perigosa” de migrar um projeto grande uma vez com poucas ressalvas. A gente realmente parabeniza o trabalho das equipes de Dart and Flutter nesse sentido;
- É possível fazer muita coisa em duas semanas! Organizamos a equipe de uma maneira que a gente não só conseguiu fazer a migração, mas ainda tivemos tempo de preparar o terreno para que novas pessoas trabalhassem no código assim que migrássemos;
- O próprio Flutter ajudou nisso também. Tínhamos muitos widgets que temíamos que parassem de funcionar após a migração, devido à complexidade – mas tudo funcionou bem. Só tivemos problemas com campos anuláveis imprevistos, provenientes de APIs que foram mapeadas incorretamente pela ferramenta;
- A partir de agora, vamos tomar muito mais cuidado com todos os novos módulos que escrevermos. Principalmente, sobre o problema de dependência, que era algo de anos atrás e, finalmente, tivemos a chance de pelo menos evitar que nos atrapalhe ainda mais no futuro.
Esperamos que você tenha gostado desta primeira parte da nossa jornada, e fique atento para a próxima! Falaremos sobre a nova arquitetura que definimos para os módulos: dados, domínio e UI.
Texto original disponível em Writing the new iFood for Partners — Part 1: Flutter 2.0