GoSuda

Go e o Ecossistema OpenAPI

By iwanhae
views ...

Introdução

Ao desenvolver um servidor de Backend de Produção na linguagem Go, um dos primeiros desafios que a maioria dos desenvolvedores encontra é o seguinte:

Como documentar a API...?

Ao pesquisar um pouco sobre o assunto, percebe-se que é vantajoso escrever a documentação de acordo com a especificação OpenAPI, e naturalmente busca-se uma biblioteca que se integre com o OpenAPI. No entanto, mesmo após essa decisão, surge o próximo problema.

Existem muitas bibliotecas relacionadas ao OpenAPI... Qual devo usar...?

Este documento é uma breve introdução a bibliotecas, elaborada para iniciantes em Go que estejam enfrentando essa situação. O documento foi escrito com base no final de 2024, e como o ecossistema da linguagem está em constante mudança, recomenda-se que seja usado como referência e que se acompanhe sempre as últimas atualizações.

Estratégias das Bibliotecas para OpenAPI

Como você já deve saber, OpenAPI é uma especificação para definir e documentar APIs REST de forma clara. Ao definir endpoints, requisições e formatos de resposta da API em YAML ou JSON, ela não só auxilia os desenvolvedores, mas também automatiza a geração de código para o frontend e backend, reduzindo repetições desnecessárias e minimizando pequenos erros humanos.

Para integrar o OpenAPI de forma natural aos projetos, as bibliotecas do ecossistema Go adotam principalmente as três estratégias a seguir.

1. Combinar comentários Go com a documentação da especificação OpenAPI

Uma das dificuldades ao desenvolver uma API de acordo com o OpenAPI é que a documentação real e o código que a implementa existem em arquivos separados, em locais completamente distintos. Isso leva a situações frequentes em que o código é atualizado, mas a documentação não, ou a documentação é atualizada, mas o código não.

Para dar um exemplo simples:

  1. Se a lógica de uma API for modificada em um arquivo chamado ./internal/server/user.go,
  2. mas a documentação real estiver em ./openapi3.yaml, a alteração na documentação pode ser esquecida por engano.
  3. Se um Pull Request for enviado sem que o problema dessas alterações seja percebido, e os colegas revisarem o código,
  4. os revisores também não verão as alterações em ./openapi3.yaml. Isso pode levar a um problema em que a especificação da API permanece a mesma, mas a implementação real da API é alterada.

Escrever a documentação da API em forma de comentários Go pode resolver esse problema até certo ponto. Como o código e a documentação estão no mesmo lugar, é possível atualizar os comentários junto com o código. Existem ferramentas que geram automaticamente a documentação da especificação OpenAPI com base nesses comentários.

Um projeto representativo é o Swag. O Swag analisa os comentários do código Go para gerar documentação no formato OpenAPI 2. O uso é simples: basta escrever os comentários acima das funções do handler, seguindo o formato definido por cada biblioteca.

 1// @Summary Criar usuário
 2// @Description Cria um novo usuário.
 3// @Tags Usuários
 4// @Accept json
 5// @Produce json
 6// @Param user body models.User true "Informações do usuário"
 7// @Success 200 {object} models.User
 8// @Failure 400 {object} models.ErrorResponse
 9// @Router /users [post]
10func CreateUser(c *gin.Context) {
11    // ...
12}

Ao escrever comentários dessa forma, a ferramenta CLI Swag os analisa para gerar a documentação OpenAPI 2. Geralmente, essa tarefa é realizada durante o processo de CI, e a documentação da especificação OpenAPI gerada é implantada em um repositório Git, no resultado final da build ou em um sistema externo de gerenciamento de documentação de API, para ser utilizada na colaboração com outros projetos.

Vantagens:

  • Como os comentários estão junto ao código, a probabilidade de a documentação e a implementação real do código serem inconsistentes é reduzida.
  • A documentação pode ser feita de forma simples e livre, apenas com comentários, sem a necessidade de ferramentas adicionais ou configurações complexas.
  • Como os comentários não afetam a lógica real da API, é ótimo para adicionar funcionalidades temporárias que seriam difíceis de expor na documentação.

Desvantagens:

  • O aumento do número de linhas de comentários pode diminuir a legibilidade de um único arquivo de código.
  • Pode ser difícil expressar todas as especificações da API na forma de comentários.
  • Como a documentação não impõe o código, não há garantia de que a documentação OpenAPI e a lógica real sejam consistentes.

2. Gerar código Go a partir da documentação da especificação OpenAPI

Existe também uma abordagem em que a "Single Source of Truth (SSOT)" não é o código Go, mas sim a documentação. Isso envolve primeiro definir a especificação OpenAPI e, em seguida, gerar o código Go com base no conteúdo definido. Como a especificação da API gera o código, ela pode forçar culturalmente a equipe de desenvolvimento a projetar a API primeiro. Além disso, como a definição da especificação da API é o primeiro passo no processo de desenvolvimento, ela tem a vantagem de prevenir precocemente problemas como a percepção de omissões após a conclusão do desenvolvimento, levando a alterações na especificação da API e a modificações em todo o código.

Projetos representativos que adotam essa abordagem incluem oapi-codegen e OpenAPI Generator. O uso é simples:

  1. Escreva um documento YAML ou JSON de acordo com a especificação OpenAPI.
  2. Execute a CLI.
  3. O código Go stub correspondente será gerado.
  4. Agora, basta implementar a lógica detalhada para cada API, para que o stub possa utilizá-la.

A seguir, um exemplo de código gerado pelo oapi-codegen:

1// StrictServerInterface representa todos os manipuladores de servidor.
2type StrictServerInterface interface {
3	// ...
4	// Retorna todos os pets
5	// (GET /pets)
6	FindPets(ctx context.Context, request FindPetsRequestObject) (FindPetsResponseObject, error)
7	// ...
8}

A partir da interface acima, o código gerado pelo oapi-codegen executa lógicas como a análise de query parameters, header e body, além de validação, e invoca o método apropriado declarado na interface. O usuário apenas precisa implementar a concretização dessa interface para concluir o trabalho de implementação da API.

Vantagens:

  • Como a especificação é definida primeiro e o desenvolvimento prossegue, é vantajoso para a colaboração paralela entre várias equipes.
  • O código para partes que eram trabalhos repetitivos e manuais é gerado automaticamente, o que aumenta a eficiência do trabalho e ainda é vantajoso para a depuração.
  • É fácil garantir que a documentação e a estrutura do código estejam sempre consistentes.

Desvantagens:

  • Se houver desconhecimento da própria especificação OpenAPI, haverá uma curva de aprendizado inicial.
  • Como a estrutura do código que manipula a API é gerada automaticamente pelo projeto, pode ser difícil lidar com casos que exigem customização.

Comentário do autor. Em outubro de 2024, o código Go gerado pelo OpenAPI Generator não só impõe a lógica da API, mas também a estrutura de todo o projeto, tornando a estrutura rígida. Ele gera um tipo de código inadequado para adicionar diversas funcionalidades necessárias em um ambiente de Produção real. Para aqueles que adotam essa abordagem, recomendo fortemente o uso do oapi-codegen. O autor utiliza oapi-codegen + echo + StrictServerInterface.

3. Gerar documentação da especificação OpenAPI a partir do código Go

Quando dezenas ou centenas de pessoas desenvolvem para o mesmo servidor, é inevitável que a uniformidade entre as APIs individuais seja comprometida. Em um exemplo intuitivo, se as especificações para mais de 100 endpoints de API forem declaradas em um único arquivo YAML OpenAPI, esse arquivo se tornará um monstro com mais de 10.000 linhas. Ao declarar novos endpoints de API, inevitavelmente haverá declarações duplicadas do mesmo modelo, omissão de alguns campos, ou o surgimento de nomes de Path que não seguem as convenções, resultando na quebra da uniformidade geral da API.

Para resolver essa questão, pode-se designar um Owner para gerenciar o arquivo YAML OpenAPI, ou desenvolver um Linter para capturar automaticamente essas inconsistências durante o processo de CI. No entanto, também é possível definir uma Domain-specific language (DSL) em Go para forçar que todas as APIs mantenham uma uniformidade consistente.

Um projeto representativo que utiliza essa técnica é o Kubernetes (que a constrói internamente sem bibliotecas externas), e também pode ser utilizada com projetos como go-restful e goa. A seguir, um exemplo de uso do goa:

 1var _ = Service("user", func() {
 2    Method("create", func() {
 3        Payload(UserPayload)
 4        Result(User)
 5        HTTP(func() {
 6            POST("/users")
 7            Response(StatusOK)
 8        })
 9    })
10})

Ao escrever um código Go compilável como o exemplo acima, obtém-se a vantagem de que a implementação e a definição da documentação para a API POST /users são concluídas simultaneamente.

Vantagens:

  • Como tudo deriva do código, é fácil manter a consistência da API em todo o projeto.
  • Ao utilizar o sistema de tipos fortes do Go, é possível obter uma especificação mais precisa e inequívoca do que ao utilizar todas as funcionalidades do OpenAPI3.

Desvantagens:

  • É necessário aprender a DSL definida por cada framework, e pode ser difícil aplicá-la ao código existente.
  • A liberdade e flexibilidade podem ser reduzidas, pois é preciso seguir as regras impostas pelo framework.

Conclusão

Cada método possui suas vantagens e desvantagens, e é crucial escolher a abordagem mais adequada de acordo com os requisitos do projeto e a preferência da equipe. O mais importante não é qual método é o melhor, mas sim realizar um julgamento de valor sobre qual solução é a mais apropriada para a sua situação atual, a fim de aumentar a produtividade do desenvolvimento e desfrutar de um rápido término do expediente e de um satisfatório equilíbrio entre vida pessoal e profissional.

Embora este artigo tenha sido escrito em outubro de 2024, o ecossistema Go e OpenAPI está em constante evolução. Portanto, considerando o intervalo de tempo entre a leitura deste texto, é recomendável acompanhar continuamente as atualizações e as mudanças nas vantagens e desvantagens de cada biblioteca e projeto.

Tenham uma feliz vida em Go! 😘