Malka (Daniel Lemos) / Minhas experiências com Organização de Projetos Go

Created Tue, 24 Jun 2025 16:49:10 -0300 Modified Tue, 24 Jun 2025 19:14:47 -0300
2009 Words

Várias pessoas em uma sala desenvolendo software - Gerado por IA

Organização de Projetos Go: Liberdade, Padronização e os Impactos no Desenvolvimento

A estrutura de código em projetos Go é um tema de constante debate em qualquer ambiente de desenvolvimento. Minhas próprias experiências profissionais confirmam isso: já presenciei discussões acaloradas entre desenvolvedores que defendiam a total liberdade da linguagem, sem a imposição de padrões rígidos, e outros que viam a necessidade urgente de criar uma padronização interna. Para estes últimos, um padrão seria o guia essencial para rapidamente compreender novos projetos, colaborar de imediato e ter um ponto de referência sólido ao enfrentar desafios estruturais ou de escala. Curiosamente, essa é uma das áreas onde Go oferece grande liberdade, que pode ser tanto uma bênção quanto uma maldição. Diferente de outras linguagens e ecossistemas que frequentemente impõem uma estrutura rígida através de frameworks ou convenções fortemente acopladas, Go não dita uma arquitetura de projeto obrigatória. Essa característica confere uma flexibilidade incrível, permitindo que os desenvolvedores adaptem a estrutura às necessidades específicas de cada aplicação. No entanto, essa mesma liberdade pode se tornar uma fonte de confusão para quem chega na linguagem, especialmente vindo de ambientes onde a estrutura de código é pré-definida e amplamente aceita.

A Importância dos Padrões

A ausência de uma estrutura de projeto imposta por Go não significa que a padronização é desnecessária; pelo contrário, ela se torna ainda mais vital. Adotar um padrão de organização de pastas e código, seja ele inspirado nas convenções da comunidade Go (como as diretrizes do projeto standard-layout, entre outros) ou em um padrão interno pré-definido pela empresa, é crucial para a saúde de um projeto a longo prazo. Essa padronização serve como um guia implícito para todos os desenvolvedores. Provavelmente, assim como eu, você já se perdeu tentando navegar em um ou outro projeto Go, ou se sua própria base de código evoluiu para um grande emaranhado de dependências. Criar uma estrutura de projeto eficaz é, de fato, uma das habilidades mais subestimadas, porém cruciais, para desenvolvedores Go.

Um projeto Go bem organizado não é apenas algo bom de se ter, isso é essencial e oferece benefícios sérios que impactam diretamente a sua produtividade e a de outros. A sustentabilidade do projeto pode ser impactada das seguintes formas:

  • Integração mais fácil para novos membros da equipe: Um padrão claro elimina a necessidade de que novos desenvolvedores enviem mensagens regulares perguntando onde ou como fazer algo. Eles podem rapidamente entender onde encontrar lógica de negócios, configurações, modelos de dados ou integrações na camada de infra, por exemplo. Um diretório internal para a lógica principal e pkg para utilitários reutilizáveis, como o standard-layout sugere, cria um mapa mental instantâneo.
  • Melhor capacidade de teste: Quando o código é organizado em módulos lógicos com responsabilidades bem definidas, ele se torna naturalmente mais testável. Um diretório internal/pay, por exemplo, com serviços que recebem interfaces, em vez de implementações concretas, permite testes unitários e de integração eficazes sem uma confusão de dependências emaranhadas. Além disso já fornece para o Desenvolvedor noção do que aquele pequeno módulo faz caso escale ao ponto de ser necessário segmentar em outro serviço próprio.
  • Melhor manutenibilidade: Seu “eu futuro” agradecerá ao seu “eu presente” por ter um código onde as funcionalidades estão separadas e bem nomeadas. Manter um projeto onde a autenticação está no diretório internal/auth e os handlers HTTP em interface/api/handlers é infinitamente mais fácil do que vasculhar arquivos aleatórios. Isso evita aquela dor de cabeça de não saber o que você estava pensando quando escreveu determinado trecho de código.
  • Fluxo de dependência claro: Uma boa organização previne os temidos erros de importação cíclica, que ocorrem quando dois ou mais pacotes dependem um do outro, criando uma referência circular que o compilador não consegue resolver. Ao separar domínios e camadas (por exemplo, app, domain, interface), você estabelece um fluxo de dependência unidirecional, onde uma camada superior depende da inferior, mas nunca o contrário.
  • Implantação simplificada: Com uma estrutura lógica, os artefatos de build fazem sentido. O binário gerado pode ser colocado junto com arquivos de configuração, e o Dockerfile pode referenciar facilmente os caminhos corretos, agilizando o processo de CI/CD.

A previsibilidade que a padronização oferece acelera o desenvolvimento, a depuração e a manutenção, pois todos sabem onde procurar por funcionalidades específicas ou onde adicionar novo código. Já observei bases de código onde essa flexibilidade se transformou em caos: uma organização ruim de projeto inevitavelmente leva a dependências circulares, código impossível de testar e dores de cabeça com manutenção que fazem você querer refatorar tudo por diversas vezes enquanto seu programa cresce de forma descontrolada.

Quando a Não Padronização Vira Barreira

Por outro lado, quando a organização de um projeto Go é feita em total desacordo com o que é comum para o ecossistema da linguagem, ou mesmo sem qualquer padrão interno consistente, os impactos podem ser severos. Projetos que ignoram as convenções da comunidade Go e criam estruturas de pastas e módulos completamente únicas geram uma barreira significativa para qualquer desenvolvedor que não esteja familiarizado com aquele projeto específico. Há quem defenda a ideia de que “se leu e não entendeu, não sabe programar”, mas esses argumentos frequentemente ignoram a vasta jornada de conhecimento percorrida pelos mais experientes. Muitas vezes, os mais confundidos são os novatos, que se deparam com padrões tão divergentes em cada empresa que ingressam.

O processo de onboarding se torna um pesadelo: novos membros gastam tempo excessivo tentando decifrar a lógica da organização, perdidos em um mar de diretórios e arquivos que não seguem uma lógica reconhecível, muitas vezes ambíguos e fonte de mais confusão do que esclarecimento. É comum que a documentação, quando existe, careça de exemplos claros, esquecendo que o que é óbvio para quem a concebeu nem sempre será transparente para outros com perspectivas e formas de organizar ideias distintas. Essa frustração pode levar à desmotivação e à lentidão no desenvolvimento. Além disso, a falta de um padrão comum dificulta a colaboração, a revisão de código e a integração com ferramentas e bibliotecas que esperam certas convenções. Resumidamente, o que deveria ser a liberdade de Go se transforma em um obstáculo, sabotando a eficiência e a escalabilidade do projeto. Em casos extremos, isso pode até mesmo quebrar o crescimento do projeto, gerando retrabalhos futuros com a necessidade de trocas de padrões e a perda de unidade na empresa devido a projetos diversos com padrões divergentes.

Padrão sem Padronizar

A organização do código em Go, como discutimos, é uma faca de dois gumes: a liberdade que oferece pode se transformar em um desafio sem um norte. Se você chegou até aqui, é provável que esteja buscando não apenas a teoria, mas soluções práticas para essa questão. Embora Go não imponha um padrão rígido, a comunidade e grandes empresas como Uber estabeleceram convenções que se tornaram referências valiosas.

É comum que desenvolvedores, inspirados por esses exemplos de sucesso, desenvolvam seus próprios padrões para projetos. Em minhas experiências profissionais, coletei o que considerei mais eficaz para criar uma estrutura que, embora siga as convenções da comunidade Go, incorpora pequenas adaptações para maior clareza e praticidade. Meu objetivo é aproximar você do que já encontrei no mercado e oferecer ideias ou, quem sabe, esclarecer padrões já bem conhecidos.

Meu Padrão Híbrido: Clareza Inspirada na Comunidade Go

Abaixo, apresento a estrutura que costumo aplicar em novos projetos Go (pessoais e profissionais quando há abertura para tal). Ela se baseia no que o Standard Layout propõe, mas com algumas omissões e adaptações para se adequar às minhas preferências e às necessidades que encontro no dia a dia.

project/
├── cmd/            # Pontos de entrada principais (main) da aplicação
├── internal/       # Código privado da aplicação (lógica de negócio, repositórios, etc.)
├── pkg/            # Bibliotecas e pacotes públicos/externos reutilizáveis
├── test/           # Testes de ponta a ponta (e2e) e outros testes de alto nível
├── docs/           # Documentação do projeto
├── go.mod          # Definição do módulo Go
├── go.sum          # Checksums das dependências do módulo Go
├── README.md       # Documentação inicial do projeto
├── Dockerfile      # Instruções para construir a imagem Docker
└── Makefile        # Automação de tarefas de build e desenvolvimento

Sigo essa estrutura como base, e a ausência de alguns diretórios comumente vistos no Standard Layout se deve a escolhas pontuais e justificadas por diretrizes que adotei:

  • api: Prefiro não utilizar este diretório na raiz. Quando há definições de API (como Protobufs ou schemas OpenAPI), costumo aninhá-las dentro de docs/proto ou docs/swagger;
  • docs: Este diretório funciona como uma central de documentação. Dentro dele, costumo organizar arquivos Markdown (.md) que explicam o projeto, guias de contribuição e outras documentações técnicas. É também onde outros formatos de documentação podem ser encontrados, sejam imagens, C4 Model, etc;
  • configs: Mantenho as configurações da aplicação dentro de internal/configs. Minha justificativa é que algumas configurações são intrínsecas ao funcionamento privado do projeto e não deveriam estar expostas fora do local onde o código privado reside;
  • test: Este diretório é reservado para testes de alto nível, como testes de ponta a ponta (E2E) ou de integração com sistemas externos (utilizando, por exemplo, Testcontainers). Normalmente, meus projetos nascem sem ele, e sua criação ocorre organicamente conforme surge a necessidade de testar cenários completos ou integrações complexas;
  • examples e scripts: Geralmente, não vejo a necessidade de diretórios dedicados a exemplos ou scripts avulsos. No entanto, ainda utilizo scripts – de forma mais rara –, preferindo incorporar a automação de tarefas de manutenção do serviço em uma aplicação CLI própria, mantendo a responsabilidade de scripts mais complexos dentro do próprio código Go;
  • deployments: Nunca utilizei esse diretório. Sou do time que prefere manter o Dockerfile na raiz do projeto, simplificando a identificação e o processo de build da imagem;
  • vendor: Da mesma forma que prefiro o Dockerfile na raiz, confio minhas dependências inteiramente ao go.mod e go.sum. O diretório vendor é gerado apenas se necessário para cenários específicos, como builds em ambientes restritos ou ferramentas legadas, mas não faz parte da estrutura padrão que defino.

Seguindo essa estrutura, consigo resolver a maioria dos desafios de organização que enfrento no dia a dia, promovendo clareza e manutenibilidade.

Flexibilidade vs Doutrina: Cenários Corporativos

É importante ressaltar que, apesar da minha preferência por este padrão, dependo do contexto da empresa em que estou atuando. Já lutei bastante e sempre levo minha opinião e minhas experiências para os Chapters de Go no qual participo. No entanto, não sou do tipo que insiste em regras inflexíveis. A experiência me ensinou que, muitas vezes, minha paz pessoal e a capacidade de colaborar efetivamente valem mais do que tentar convencer uma equipe inteira de seniors, staffs e outros que o onboarding está dificultoso ou que projetos existentes não seguem uma estrutura “ideal”. Entregar-me ao aprendizado e absorver o guideline interno, mesmo que seja totalmente contrário às práticas da comunidade, é uma postura que adoto. Afinal, a vivência na programação me mostrou que a paz e a produtividade da equipe podem ser mais valiosas do que impor uma única visão, especialmente quando o objetivo é contribuir para o crescimento e a unidade da empresa.

Mas, como em todo processo de aprendizado e crescimento, o caminho nem sempre é um mar de rosas. Um erro comum, e que muitos de nós, incluindo eu, já cometeram ao iniciar na linguagem, é organizar pacotes por MVC (Model-View-Controller) ou outras arquiteturas baseadas em camadas genéricas, em vez de por funcionalidade. Em Go, o paradigma de organização focado no domínio de negócio e nas capacidades que o código oferece tende a ser mais eficaz. Lembre-se da máxima:

“Projete a arquitetura, nomeie os componentes e documente os detalhes.”

Por isso, minha principal recomendação é começar o projeto de forma pequena, sem uma estrutura excessivamente robusta de imediato – um main.go na raiz é um excelente ponto de partida. À medida que o código cresce e as responsabilidades se tornam mais claras, você pode, e deve, iniciar o processo de organização, refatorando e agrupando o código de forma lógica. Esse crescimento orgânico, guiado pelas necessidades reais do projeto, é o que leva a uma estrutura verdadeiramente funcional e adaptável.

Referências, Links para Consulta e Aprendizado: