Go + Project Layout
Organizando a Casa— Artigo 2
Postman collection, código do artigo, diff com o artigo 1.
Sumário: Desenvolvimento de APIs em Go
Próximo Artigo: Go + Docker — Dockerizando uma API
Artigo Anterior: Go + Chi — Criando uma API REST simples
Introdução
No artigo anterior, criamos uma API simples utilizando o framework chi. Seguimos uma abordagem simplista que ignorou muitas boas práticas de programação, as quais vamos começar a revisar. Neste artigo, iremos organizar os pacotes seguindo os padrões do project-layout e implementar alguns pacotes adicionais para aumentar a coesão da nossa aplicação. É importante ressaltar que ainda não resolveremos todos os problemas neste artigo, a fim de não prolongá-lo demais e deixar conteúdo para outros episódios.
Project Layout
Go é uma linguagem que estabelece padrões para algumas coisas, enquanto deixa outras completamente abertas. Uma das questões sem padrão é a estrutura de organização de pastas. Os desenvolvedores têm o poder de escolher qualquer forma de estruturar suas aplicações, o que possui seus aspectos positivos e negativos.
Minha experiência como desenvolvedor Go mostrou que a falta de um padrão muitas vezes é um problema quando se trabalha em equipe. Percebi que por vezes isso resulta em um sintoma frequente no fluxo de desenvolvimento, que é a realização constante de refatorações, sempre movendo as coisas de lugar. Testemunhei esse efeito em mais de um time e em diferentes empresas nas quais trabalhei. Isso ocorre porque cada indivíduo possui seu próprio background, e quando não há um padrão definido, as subjetividades começam a influenciar na organização, levando as pessoas a tentarem deixar as coisas do seu próprio jeito.
Embora o Go não possua um padrão específico para essa questão, um grupo de pessoas decidiu intervir e propor um projeto chamado project layout.
Primeira linha do README.md no repositório do project-layout no Github:
This is a basic layout for Go application projects. It’s not an official standard defined by the core Go dev team.
Organização de Pastas
No project-layout são definidas várias pastas que podem ser usadas em situações específicas. No entanto, devido à baixa complexidade da nossa API até o momento, utilizaremos apenas as pastas cmd e internal da raiz, conforme demonstrado organização seguinte. Além disso, é possível notar a criação de várias outras pastas dentro de internal que abordaremos em detalhes mais adiante.
Dentro da pasta cmd, serão adicionados todos os pontos de entrada da aplicação, ou seja, todas as formas de executar aquele código. Por exemplo, podemos ter um ponto de entrada para uma API que recebe e retorna JSON, outro ponto de entrada para uma versão que utiliza protobuf e até mesmo um ponto de entrada para executar testes funcionais daquela API. As possibilidades são diversas.
Agora que a responsabilidade do diretório cmd é apenas inicializar a aplicação, nossa função main ficou mais concisa e utiliza outros pacotes Go que possuem suas próprias responsabilidades. Toda a lógica de CRUD, decode, encode e definição de endpoints foram movidas para outras camadas.
Observe que este código ainda está longe de ser a versão final, e iremos analisar essa organização mais a fundo quando abordarmos arquiteturas em camadas.
Melhorando a Coesão Dentro da Internal
Aqui, a pasta internal está sendo usada para encapsular nossa aplicação. Na verdade, esse é o único padrão real do Go que foi mostrado até agora. Esse padrão garante que qualquer objeto definido dentro dessa pasta seja inacessível para observadores externos. Optamos por colocar nossa API dentro da pasta internal porque, ao trabalhar com várias aplicações simultaneamente, isso evita que uma aplicação faça referência à componentes de outra.
Na raiz da pasta internal, foram criados os pacotes encode e product. O pacote encode contém apenas um arquivo com a função responsável por escrever objetos em formato JSON nas respostas da API. Devido a essa função ser genérica, optei por mantê-la fora do pacote product.
Por outro lado, o pacote product possui vários subpacotes que estão relacionados a componentes que interagem diretamente com o conceito de Product da API, responsável pelo gerenciamento de pacotes.
Segue uma relação dos subpacotes e suas responsabilidades.
- productdb: nesse pacote, incluímos o código responsável por emular um banco de dados em memória.
- productdecode: transferimos para cá o código responsável por realizar a deserialização dos dados de entrada da API em estruturas Go.
- productdomain: movemos para cá a definição da nossa entidade de domínio Product e criamos um pacote de serviço que contém a lógica do CRUD, que anteriormente estava na função main.
- producthttp: transferimos para cá a definição de todos os handlers que antes estavam na função main.
Vale ressaltar que essas mudanças têm como objetivo aumentar a coesão dos pacotes. Além disso, é importante destacar que o código de CRUD, que anteriormente estava diretamente na definição dos endpoints, foi movido para uma nova camada de serviços. Nessa camada, foi criada uma struct (que nesse caso se comporta como uma classe) responsável por realizar o CRUD, representando o local das regras de negócio.
A separação da regra de negócio nessa camada de serviço vai além do simples aumento da coesão da aplicação. No entanto, abordaremos esse aspecto em um episódio posterior.
Conclusões e Próximas Etapas
Neste artigo, vimos a aplicação do project-layout em uma API desenvolvida em Go e também melhoramos um pouco a coesão de nossa aplicação. Embora o project-layout não seja um padrão oficial do Go, considero benéfico a sua utilização, pois ele mapeia muitas questões comuns no desenvolvimento de APIs. Até o momento, estamos utilizando apenas os pacotes cmd e internal, mas à medida que avançamos nesta série, provavelmente precisaremos de outras pastas, como config, docs e etc.
No próximo artigo, abordaremos a dockerização de uma API Go. Embora seja um processo simples, ainda é um tópico muito importante no mundo do desenvolvimento de software.