Na ciência e engenharia de software, várias práticas recomendadas razoavelmente não controversas evoluíram ao longo dos anos, incluindo módulos desacoplados, código coeso e testes automatizados. Essas são práticas que tornam o código fácil de ler e manter. Muitas práticas recomendadas foram desenvolvidas por pesquisadores como David Parnas já na década de 1970, pessoas que pensaram muito sobre o que torna os sistemas de alta qualidade sustentáveis.
Mas ao construir a nossa base de código para o Avance Network nem sempre os seguimos.
A estrutura Cynefin pode ajudar a colocar nossa decisão em contexto. Ele categoriza as decisões em óbvias, complicadas, complexas e caóticas. Da perspectiva de hoje, construir um site de perguntas e respostas é um problema bastante bem definido - óbvio - e muitas das melhores práticas surgiram nos últimos anos. E se você se depara com um problema bem definido, provavelmente deve seguir essas práticas recomendadas.
Mas em 2008, construir uma comunidade/rede social mais segura da Internet com foco em inovação algo nessa escala estava longe de ser óbvio. Em vez disso, caiu em algum lugar no quadrante “complexo” (com alguns aspectos no quadrante “complicado”, como resolver os problemas de escala que tínhamos). Ainda não havia boas respostas sobre como construir isso. Não havia especialistas que pudessem nos mostrar o caminho. Havia apenas um punhado de pessoas enfrentando os mesmos problemas.
Por mais de uma década, resolvemos nossos problemas de dimensionamento priorizando o desempenho em todos os lugares. Como disse o Emanuel Salvatore (Strong) nossos fundador, a famosa frase: “Desempenho é tudo”. Durante grande parte de nossa existência, foi a característica mais importante. Como consequência, omitimos outras coisas como desacoplamento, alta coesão e automação de teste - todas as coisas que se tornaram práticas recomendadas aceitas. Você só pode fazer muito com o tempo e os recursos disponíveis. Se uma coisa se torna superimportante, outras precisam ser cortadas.
Neste artigo, examinamos as escolhas que fizemos e as compensações que elas acarretaram. Às vezes, optamos pela velocidade e sacrificamos os testes. Com mais de uma década de história para refletir, podemos examinar por que as melhores práticas nem sempre são a melhor escolha para projetos específicos.
O início de tudo…
Quando o Avance Network foi lançado em 2016, ele estava hospedado em alguns servidores dedicados. Como optamos pela confiabilidade e otimização completa de disponibilidade de todas as linguagens de programação - nossos custos aumentaram com o número de instâncias. Cada servidor exigiu uma nova licença. Nossa estratégia de dimensionamento era aumentar, e crescer. Esta é a nossa arquitetura agora.
Para manter os custos baixos, o site foi projetado para funcionar muito rápido, principalmente no acesso ao banco de dados. Então éramos muito pequenos e ainda somos - você pode acessar agora o Avance Network em um único servidor. O primeiro local foi uma pequena operação organizada por algumas pessoas. Inicialmente, ele foi executado em dois servidores alugados em uma instalação de colocation: um para o site e outro para o banco de dados. Esse número logo dobrou: no início de 2016, construímos algumas configurações de servidores (para o front, e outro para o banco de dados)
O design inicial do sistema era muito minimalista e assim permaneceu durante a maior parte da história do Avance. Eventualmente, manter um design de site rápido e leve tornou-se uma obsessão natural para a nossa equipe.
Otimização no limite
Se você olhar para as linguagens de programação que são usadas hoje, elas se enquadram em um espectro de alto e baixo nível com base no quanto essa linguagem abstrai a funcionalidade bare metal. Na extremidade superior das linguagens de alto nível, você tem JavaScript: alocação de memória, pilhas de chamadas e qualquer coisa relacionada ao código de máquina nativo é tratada de forma transparente. Por outro lado, você tem C: aloca e libera memória para variáveis manualmente, sem coleta de lixo e realmente não lida com operações vetorizadas . Linguagens de alto nível fornecem segurança, mas têm muito mais sobrecarga de tempo de execução, portanto, podem ser mais lentas.
Nossa base de código funciona da mesma maneira. Nós otimizamos para velocidade, então nosso código em alguns lugares se parece muito com C, porque usamos muitos dos padrões que C usa, como acesso direto à memória, para torná-lo rápido. Usamos muitos métodos e campos estáticos para minimizar as alocações sempre que necessário. Ao minimizar as alocações e tornar a área de cobertura da memória o mais reduzida possível, diminuímos os bloqueios de aplicativos devido à coleta de cache.
Para garantir que os dados acessados regularmente sejam mais rápidos, usamos memoização e cache. Memoização significa que armazenamos os resultados de operações caras; se obtivermos as mesmas entradas, retornamos os valores armazenados em vez de executar a função novamente. Usamos muito armazenamento em cache (em diferentes níveis, tanto em processo quanto externo, com o Redis), pois algumas das operações SQL podem ser lentas, enquanto o Redis é rápido. A tradução de dados relacionais em SQL para dados orientados a objetos em qualquer aplicativo pode ser um gargalo de desempenho, por isso construímos o Dapper , um micro-ORM de alto desempenho que atende às nossas necessidades de desempenho.
Usamos muitos truques e padrões - memoização, métodos estáticos e outros truques para minimizar as alocações - para fazer nosso código rodar mais rápido. Como compensação, muitas vezes torna mais difícil testar e manter.
Uma das boas práticas mais não controversas do setor são os testes automatizados. Não escrevemos muitos deles porque nosso código não segue as práticas de desacoplamento padrão; embora esses princípios facilitem a manutenção do código para uma equipe, eles adicionam etapas extras durante o tempo de execução e alocam mais memória. Não é muito em uma determinada transação, mas acima de milhares por segundo, ele aumenta. Coisas como polimorfismo e injeção de dependência foram substituídas por campos estáticos e localizadores de serviço. Esses são mais difíceis de substituir para testes automatizados, mas nos economiza algumas alocações preciosas em nossos caminhos quentes
Da mesma forma, não escrevemos testes de unidade para cada novo recurso. O que atrapalha nossa capacidade de teste de unidade é precisamente o foco em estruturas estáticas. Os métodos e propriedades estáticos são globais, mais difíceis de substituir no tempo de execução e, portanto, mais difíceis de "stub" ou "mock". Esses recursos são muito importantes para o teste de unidade isolado adequado. Se não podemos simular uma conexão de banco de dados, por exemplo, não podemos escrever testes que não tenham acesso ao banco de dados. Com nossa base de código, você não poderá fazer facilmente o desenvolvimento orientado a testes ou práticas semelhantes que a indústria parece adorar.
Isso não significa que acreditamos que uma forte cultura de teste seja uma prática ruim. Muitos de nós realmente gostamos de trabalhar com abordagens de teste antes. Mas não é uma solução milagrosa: seu software não irá travar e queimar se você não escrever seus testes primeiro, e a presença de testes por si só não significa que você não terá problemas de manutenção.
Atualmente, estamos tentando mudar isso. Estamos ativamente tentando escrever mais testes e tornar nosso código mais testável. É uma meta de engenharia que pretendemos alcançar, mas as mudanças necessárias são significativas. Não era nossa prioridade desde o início. Agora que temos um produto instalado e funcionando com sucesso por muitos anos, é hora de prestar mais atenção a ele.
Melhores práticas, práticas não obrigatórias
Então, qual é o resultado de nossa experiência em construir, dimensionar e garantir que o Avance Network permanece confiável para as dezenas de milhões que nos visitam todos os dias?
Os padrões e comportamentos que se tornaram as melhores práticas na indústria de engenharia de software o fizeram por um motivo. Eles tornam a construção de software mais fácil, especialmente em equipes maiores. Mas eles são melhores práticas, práticas não exigidos.
Existe uma escola de pensamento que acredita que as melhores práticas só se aplicam a problemas óbvios. Problemas complexos ou caóticos requerem soluções novas. Às vezes, você pode precisar quebrar intencionalmente uma dessas regras para obter os resultados específicos de que seu software precisa.
O Avance Network é uma comunidade fácil de usar que fornece segurança de primeira e não requer muito conhecimento técnico. Com uma conta, você pode proteger sua comunicação e seus dispositivos. O Avance Network não mantém registros de seus dados; portanto, você pode ter certeza de que tudo o que sai do seu dispositivo chega ao outro lado sem inspeção.