Hoje vou falar a respeito de algo que tive certa dificuldade quando comecei a mexer com Go, que é a organização de pacotes.
Para falar a verdade, esse assunto não tem uma resposta rígida. Por quê? Simples, se você navegar pelo GitHub, encontrará projetos Go com diferentes tipos de estruturação. Por exemplo, a biblioteca kafka-go da Segment utiliza uma estrutura completamente diferente da biblioteca confluent-kafka-go da Confluent.
O que a documentação oficial tem a dizer sobre pacotes?
Programas Go são organizados em pacotes. Um pacote é uma coleção de arquivos fonte dentro de um mesmo diretório e que são compilados em conjunto. Funções, tipos, variáveis e constantes definidos em um arquivo fonte são visíveis para todos outros arquivos fonte dentro de um mesmo pacote. (Tradução minha).
Vou resumir aqui um pouco a documentação oficial, sem entrar nos detalhes do uso de módulos. O código é bastante similar ao da documentação oficial, com pequenas alterações e alguma tradução.
Como podemos ver, existe o diretório raiz chamado estrutura-de-pacotes
com dois arquivos dentro, ola_mundo.go
e ola_mundo_test.go
.
Até aqui nenhum mistério.
Se observar na imagem, estamos no package main
.
Mas considerando que eu vou criar um pacote novo, chamado strutil
, todo arquivo que existir dentro dele, é visível para ele mesmo, mas não para o pacote raíz. Em outras palavras, para utilizar o código que está dentro do arquivo strutil.go
em outros pacotes, no meu caso o pacote raíz, é necessário importar o pacote no arquivo ola_mundo.go
e ola_mundo_test.go
, conforme a imagem abaixo.
Ah, um detalhe! Para que a função consiga ser exportada para outros pacotes como no caso acima, o nome da função precisa iniciar com letra maiúscula. Se eu tivesse utilizado o nome reverter()
em vez de Reverter()
, não seria possível utilizar a função dentro do arquivo ola_mundo.go
.
No mundo Go é uma boa prática comentar as funções que podem ser exportadas, pois gerando o godoc
, o comentário aparece na documentação.
Algumas pessoas podem dizer que um bom código não precisa de comentários, mas quando o código será exposto por uma documentação godoc
para alguém fazer uso da nossa API por exemplo, simples comentários podem ajudar muito nosso cliente.
O artigo Style guideline for Go packages inclusive fala que é um bom exercício ficar de olho no godoc
no início do projeto, para ler e validar se realmente faz sentido a separação de pacotes que está sendo utilizada.
Com a leitura acima, podemos entender que um pacote fica contido dentro de um diretório e caso eu necessite utilizar em outros pacotes, preciso importar o pacote com o código desejado.
Como eu devo nomear os pacotes?
Existe uma publicação feita pelo Sameer Ajmani, com detalhes sobre essa questão. Mas existem alguns pontos básicos que vou explicar aqui.
- O nome de um pacote deve ser curto e fácil de entender, mas sem comprometer a sua "intenção". Por exemplo, se eu quero escrever um pacote com utilidades relacionadas ao banco de dados, meu pacote poderia se chamar
dbutil
. Alguém pode pensar em chamar deutil
, mas como o nome é genérico demais, acaba prejudicando o entendimento do que tudo pode existir dentro do pacote. - O uso de sublinhado
_
, hífen-
ou letras maiúsculas é desencorajado. Por exemplo, posso chamar um pacote devalidacaologin
, mas eu não deveria chama-lo devalidacaoLogin
,validacao-login
ou aindavalidacao_login
. Porém, como eu disse no início da publicação, diferentes empresas podem seguir seus próprios padrões, como pode ser observado no repositório do kubernetes. Eu percebi que é difícil encontrar repositórios com pacotes cujo nome contém mais de uma palavra. Acredito ser boa abordagem se esforçar para utilizar apenas uma palavra como nome de pacote. - Abreviação é permitida, mas eu particularmente prefiro utiliza-la com cuidado. Exemplos de pacotes com nome abreviado:
cmd
,fmt
,bufio
. - Pacotes dentro de diretórios diferentes podem ter o mesmo nome. Por exemplo, posso ter
cobranca/lancamento
e tambéminvestimento/lancamento
. Mas atenção! Se esses pacotes forem importados com frequência no mesmo lugar, o ideal é que um deles seja renomeado se possível, evitando problemas de entendimento. - Os diretórios dos pacotes podem ser criados da melhor forma que você encontrar para organizar o código. Você pode criar um diretório onde colocará um determinado domínio, ou ainda um diretório onde ficará concentrado todo código relacionado à algum tipo de computação específica.
- A Jaana Dogan-Dalgaard chama atenção para algo que nós que viemos de outras linguagens acabamos estranhando. O nome do pacote deveria ser no singular e não no plural.
Afinal, existe algum padrão adotado?
Como mencionei acima, é possível encontrar diversos tipos de estruturas pelo GitHub. Mas existe um repositório com a sugestão de um padrão bem genérico que você pode ter como base, chamado golang-standards/project-layout.
Esse layout não é oficial e dependendo do tamanho do seu projeto, utiliza-lo seria complicar demais as coisas. Comece sempre da forma mais simples possível e vá adicionando novos pacotes conforme a necessidade for surgindo.
Alguns pacotes que são encontrados com frequência em projetos Go e que são explicados no README deste repositório. De forma resumida:
-
cmd
é o pacote onde você concetra os seus arquivosmain.go
. -
internal
é o pacote onde fica o código que você não quer expor para fora da aplicação ou biblioteca. Conforme vimos acima, é possível ter vários pacotesinternal
dentro de diferentes diretórios. -
pkg
é o pacote onde fica o código que pretendo expor para ser utilizado em outras aplicações. O código exposto aqui será utilizado por outras pessoas, portanto escolha com cuidado o código que ficará dentro deste pacote. Você pode encontrar aqui uma lista de repositórios que usa este padrão. -
vendor
é o pacote onde você pode adicionar suas dependências. -
api
é onde você pode colocar schemas de JSON, Swagger, etc. -
configs
é onde ficam arquivos comuns de configuração. Observe que o nome é no plural! -
scripts
é utilizado para concentrar os scripts utilizados pela aplicação. Existe um ótimo exemplo no repositório do Terraform. -
deployments
é utilizado para os arquivos docker-compose, de Terraform, etc.
Atenção para quem vem do Java como eu vim! É muito comum que a gente pense logo em criar um pacote chamado src
, porém essa prática é desencorajada, conforme a documentação do layout que citei acima.
You really don't want your Go code or Go projects to look like Java :-)
Como posso amadurecer a estrutura do meu projeto? Posso usar DDD?
Existe uma palestra, da Kat Zień que mostra como estruturar um projeto, utilizado apenas um diretório e ir evoluindo conforme a necessidade até atingir uma estrutura baseada em DDD.
Vou deixar aqui para a palestra.
Concluindo...
No fim das contas, como Go é uma linguagem nova e bastante versátil, por mais que existam alguns padrões surgindo, dependendo do projeto faz mais sentido seguir um ou outro caminho. Pra mim essa é uma característica que amo em Go, a flexibilidade.
Quando for estruturar o projeto, siga as boas práticas de comentar pacotes e funções que serão expostas, validando no godoc
se faz sentido. Converse com sua equipe e com pessoas de outros lugares, como no Slack do Gophers para entender como outros projetos estão sendo feitos e quais prós e contras foram encontrados.
Considere também aquilo que fica mais confortável para sua equipe. De nada adianta criar uma guerra interna por causa da nomenclatura de pacotes e não ter o projeto entregue!
Por favor, não esqueça de cobrir o seu código com testes. ; )
Todos artigos que utilizei para estudar e criar esta publicação foram citados acima com seus respectivos links.
Foto de de capa feita por Aleksandar Pasaric no Pexels.
Top comments (1)
Gostei do artigo!