DEV Community

Cover image for Começando com Go - parte 2 - Estruturando a API
Marcelo Pinheiro
Marcelo Pinheiro

Posted on

Começando com Go - parte 2 - Estruturando a API

Fala meus consagrados e consagradas, beleza?

Bom, como eu havia dito ontem, hoje vamos criar nosso primeiro endpoint dentro da estrutura de clean architecture que estamos utilizando. Bora lá?

Rápida explicação sobre camadas de apresentação

Dentro da pasta api que havia sido criada, vamos criar uma outra pasta que vai guardar a implementação das nossas rotas. Seria o que chamamos da camada de apresentação.

No clean architecture, podemos ter várias camadas de apresentação diferentes (por exemplo, API, Mensageria e Linha de comando), e o tchan é que, neste caso, todas elas, caso tenham o mesmo propósito, implementam o mesmo caso de uso que é onde realmente mora nossa lógica de negócio.

Isso aumenta o aproveitamento de código, facilita o teste e ajuda o projeto a crescer de forma orgânica, sem bagunçar. Fique tranquilo se isso soa grego ainda, muito em breve nós vamos ver exemplos práticos do que eu to falando. Por enquanto, vamos nos ater aos detalhes de implementação.

Criando a estrutura

Como já explicado, dentro da pasta api, crie um outro diretório chamado handler, que é o diretório que vai guardar os nossos arquivos de rotas de api e lidar com a implementação delas (por isso o nome handler)

cd management_api/api
mkdir handler
Enter fullscreen mode Exit fullscreen mode

Agora nós vamos dividir o que a nossa api tem que servir por domínio. E um domínio óbvio é o de empresas. Estamos escrevendo um Saas (software as a service, ou software como serviço, se você quiser saber mais, o link é este: https://neilpatel.com/br/blog/saas/) para ajudar empresas a se comunicarem melhor e mais efetivamente com seus clientes. Logo, uma empresa precisa conseguir se cadastrar no nosso sistema para, depois, poder cadastrar seus clientes, envios, e todo o resto em que vamos chegar. Dito isto, vamos criar o arquivo que vai lidar com todos os endpoints relacionados a empresa.

Dentro da pasta handler, crie um arquivo chamado company.go. E vamos começar a implementar nossa primeira rota. Vou fazer no mesmo modelo da parte 1, onde eu coloco o código comentado e explico o que cada linha faz.

//Especificamos que estas funções pertencem ao pacote handler.
//É dessa forma que vamos importar o que precisamos depois
package handler

import (
    "io"
    "net/http"

    "github.com/codegangsta/negroni"
    "github.com/gorilla/mux"
)

// Especificamos a função "createCompany()" e o tipo de retorno dela é http.Handler (vou explicar mais abaixo)
func createCompany() http.Handler {
        //Crio um handler http que responde uma string com o valor "teste"
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "Teste")
    })
}

//Crio uma função para especificar minhas rotas e passar a requisição para os handlers corretos
func MakeCompanyHandlers(router *mux.Router, n *negroni.Negroni) {
    router.Handle("/v1/company", n.With(
        negroni.Wrap(createCompany()),
    )).Methods("POST", "OPTIONS").Name("createCompany")
}

Enter fullscreen mode Exit fullscreen mode

Alguns pontos importantes sobre o código acima:

http.Handler

Na função createCompany, nós retornamos um http.Handler. O tipo http.Handler é responsável por captar uma requisição (http.Request) e um "escritor" de resposta (http.ResponseWriter), dessa forma ele consegue ler os dados que vieram da requisição, como por exemplo, o body, e também responder a essa requisição, enviando também o body ou status code. Esse tipo (o http.Handler) é retornado da função http.HandlerFunc, que recebe como parâmetro outra função com a implementação do handler (neste caso, passamos uma função anônima, ou seja, não demos nome para ela e a implementação dela ja está especificada) e passa para esta função estes dois objetos que especifiquei mais acima. Uma forma mais fácil de entender o conceito é se não utilizarmos uma função anônima, como abaixo:

//Essa é a função handler que contém a implementação
func createCompanyHandler(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "Teste")
}

//Essa é a função que transforma o handler acima em um http.Handler
func createCompany() http.Handler {
    //Era na linha abaixo, no lugar de createCompanyHandler, que estávamos passando a função anônima
    return http.HandlerFunc(createCompanyHandler)
}
Enter fullscreen mode Exit fullscreen mode

Eu não vou seguir assim porque separar a implementação e a transformação em http.Handler faria nosso código ficar confuso e cheio de referências desnecessárias. Mas a ideia foi explicar este ponto de forma mais clara.

MakeCompanyHandlers

Bom, a função createCompany cria uma implementação que pode ser atrelada a uma rota, mas ainda não foi atrelada a nenhuma. Por enquanto, ela é só uma função sem aplicação. E é aí que surge a MakeCompanyHandlers. Essa função recebe um objeto mux.Router(lembra, lá no arquivo main.go, que criamos um router mux? Esse cara que ela recebe) e um personagem novo, o negroni.Negroni.

O mux.Router é um roteador simples, onde eu especifico as rotas e passo um handler para tratá-las. Condiz bem com o que a gente ta precisando, né?

Mas o que raios faz esse negroni? Bom, para entender o que ele faz, precisamos entender o que é uma middleware no contexto de api.

Middlewares são processos ou lógicas que precisam ser executados sempre que uma ou mais rotas são chamadas, mas que não fazem parte da implementação destas rotas. Por exemplo, um processo de autenticação das minhas rotas. Eu poderia, em todas as rotas que eu criar, validar se o usuário está logado, por exemplo. Mas eu teria que repetir o mesmo código em todas as minhas rotas, e repetir código nunca é bom por muitas razões, entre elas, se um dia eu mudasse a minha autenticação, teria que mudar em todas as rotas que criei. Isso, além de criar acoplamento no código (acoplamento é quando códigos que não deveriam depender um do outro, dependem), atrapalhando os testes, dificultando a manutenção, etc etc etc.

É por isso que utilizamos middlewares. No caso acima, eu apenas criaria um middleware responsável por autenticar o usuário antes de chamar a rota que eu preciso. Assim, se o usuário não estivesse logado, o middleware já pararia esse cara. E a minha rota ficaria só com o código que ela tem que ficar, sem se preocupar com autenticação.

E esse é o papel do negroni: prover uma estrutura simples para criarmos middlewares. No código, eu encapsulo a nossa função createCompany com o negroni utilizando a função negroni.Wrap, que converte um http.Handler (lembra dele?) em um negroni.Handler, e depois "registro" esse negroni.Handler na instância do negroni que recebi via parâmetro da função MakeCompanyHandlers para que ela "registre" esse novo handler e possa chamá-lo quando implementarmos um middleware.

O restante é bem simples, chamo a função router.Handle com a rota que eu quero servir a implementação da nossa createCompany(no caso, v1/company), especifico quais métodos HTTP ela deve responder (POST e OPTIONS) e dou um nome para essa rota (createCompany).

Ufa ... Bora terminar de implementar isso aí.

Integrando nossa rota

Voltando para o arquivo management_api/api/main.go, a primeira coisa que vamos fazer é gerar uma instância da classe (posso chamar assim em go? Não sei. Mas vou chamar de classe mesmo assim. Depois a turma me corrige) do negroni e depois, chamar a função MakeCompanyHandlers passando como parâmetro o router que já tínhamos e a instância do negroni.

//instancia o mux e o negroni
r := mux.NewRouter()
n := negroni.New()

//Chamo a função MakeCompanyHandlers que registra nossas rotas
handler.MakeCompanyHandlers(r, n)
Enter fullscreen mode Exit fullscreen mode

o arquivo main.go inteiro fica assim

package main

import (
    "io"
    "log"
    "net/http"
    "os"
    "strconv"
    "time"

    "github.com/gorilla/context"
    "github.com/gorilla/mux"
    "github.com/marcelocpinheiro/communication-engine/management_api/config"
)

func main() {
    //instancia o mux e o negroni
    r := mux.NewRouter()
    n := negroni.New()

    //Chamo a função MakeCompanyHandlers que registra nossas rotas
    handler.MakeCompanyHandlers(r, n)

    // Repassa as rotas para o roteador
    http.Handle("/", r)

    // Cria um handler para a url "/ping", escreve "running" na tela e retorna o status 200
    r.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "Running")
        w.WriteHeader(http.StatusOK)
    })

    // Cria um logger para a aplicação
    logger := log.New(os.Stderr, "logger: ", log.Lshortfile)

    // Instancia um novo servidor com configurações específicas de timeout, endereço, log e handler
    srv := &http.Server{
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
        Addr:         ":" + strconv.Itoa(config.API_PORT),
        Handler:      context.ClearHandler(http.DefaultServeMux),
        ErrorLog:     logger,
    }

    //Printa no terminal a porta que a aplicação está ouvindo
    log.Printf("Listening on port %d", config.API_PORT)

   //inicia o servidor e, se houver erro, mata a aplicação e printa o erro
    err := srv.ListenAndServe()
    if err != nil {
        log.Fatal(err.Error())
    }
}
Enter fullscreen mode Exit fullscreen mode

E é isso. Bora rodar?

go run management_api/api/main.go
Enter fullscreen mode Exit fullscreen mode

Quando ver que tá rodando, bora criar uma requisição POST. Eu utilizo o Curl, mas você pode usar o Insomnia, o próprio Postman, fica a seu critério.

Anyway, o comando que você tem que rodar é

curl --location --request POST 'http://localhost:8080/v1/company'
Enter fullscreen mode Exit fullscreen mode

Se tudo deu certo, agora você está vendo a resposta Teste, que é o que retornamos na nossa função createCompany lá no arquivo company.go, indicando que está tudo funcionando.

Fique ligado para acompanhar a série, e siga @devmediano no instagram, devo estar postando mais sobre essa série por lá.

No próximo post vamos criar a implementação do nosso primeiro endpoint, armazenar a empresa na base de dados, retornar um json e também criar um endpoint de listagem de empresas. Até lá!

Top comments (0)