Buildando aplicações com Bazel (parte 1)

Bazel é um ferramenta criada e mantida pela Google que ajuda no processo de build de várias linguagens, sendo uma delas nosso querido Golang.

Duas de suas grandes vantagens são:

  • Build de multiplas aplicações em monorepo sem precisar ficar entrando e saindo de pastas.
  • Cache remoto das etapas de build (para mais detalhes, leia o post “Como Bazel funciona internamente“).

Embora para maioria das linguagens toda criação e manutenção dos arquivos do Bazel tenha que ser feita manualmente, para o Go temos o gazelle, uma ferramenta que nos auxilia nesse processo.

Se você ainda não tem o Bazel instalado na sua máquina, siga o tutorial do próprio site oficial segundo o OS que você utiliza. Se você já tem, execute um bazel version para garantir que você está utilizando a última versão (4.2.2).

Como aplicação exemplo, vamos utilizar o mesmo código do post Implementando uma API com gorilla/mux, mas separando as duas funções de “handler” em um novo package chamado handlers.

O estrutura de pastas/arquivos deve ser a seguinte:

- handlers
    - hello.go
    - person.go
- go.mod
- go.sum
- main.go

Para que você não precise olhar no outro post, segue como devem ficar os arquivos:

handlers/hello.go

package handlers

import (
    "encoding/json"
    "fmt"
    "net/http"

    "github.com/gorilla/mux"
)

func HandleHello(w http.ResponseWriter, rq *http.Request) {
    vars := mux.Vars(rq)

    w.Header().Add("Content-Type", "application/json")
    json.NewEncoder(w).Encode(map[string]string{
        "message": fmt.Sprintf("Hello %s", vars["name"]),
    })
}

handlers/person.go

package handlers

import (
    "encoding/json"
    "fmt"
    "net/http"
)

type Person struct {
    Name string `json:"name"`
    Age  uint8  `json:"age"`
}

func HandlePerson(w http.ResponseWriter, rq *http.Request) {
    var p Person

    err := json.NewDecoder(rq.Body).Decode(&p)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    fmt.Fprintf(w, "Nome: %s - Idade: %d\n", p.Name, p.Age)
}

main.go

package main

import (
    "net/http"

    "github.com/aprendagolang/gorilla/handlers"
    "github.com/gorilla/mux"
)

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/{name}", handlers.HandleHello).Methods("GET")
    r.HandleFunc("/person", handlers.HandlePerson).Methods("POST")

    http.ListenAndServe(":8080", r)
}

Agora é só executar go mod init github.com/aprendagolang/gorilla para gerar o arquivo go.mod, e go mod tidy para gerar o go.sum.

Com a API pronta, vamos começar criar os arquivos de configuração do Bazel.

Quando usamos o gazelle para nos auxiliar, basicamente precisamos criar dois arquivos no root do nosso projeto. Esses arquivos são o WORKSPACE, que é responsável por toda gestão das dependências do Bazel, e o arquivo BUILD, que é onde vamos definir algumas regras.

Com a criação desses dois arquivos, nossa estrutura de pastas e arquivos deve ser a seguinte agora.

- handlers
    - hello.go
    - person.go
- BUILD
- go.mod
- go.sum
- main.go
- WORKSPACE

No arquivo WORKSPACE, antes de qualquer coisa, precisamos importar/fazer load da função do Bazel chamada http_archive. Essa função será utilizada para fazer download das regras do Go e do gazelle.

Após o download dessas regras, vamos fazer o load das funções go_register_toolchains e go_rules_dependencies, que estão presentes nas regras do Go, e as funções gazelle_dependencies e go_repository que estão presentes nas regras do gazelle.

Por último, vamos chamar as funções go_rules_dependencies(), go_register_toolchains(version = "1.17.2") e gazelle_dependencies().

Com todas essas etapas descritas, o arquivo WORKSPACE deve ficar assim:

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
    name = "io_bazel_rules_go",
    sha256 = "2b1641428dff9018f9e85c0384f03ec6c10660d935b750e3fa1492a281a53b0f",
    urls = [
        "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.29.0/rules_go-v0.29.0.zip",
        "https://github.com/bazelbuild/rules_go/releases/download/v0.29.0/rules_go-v0.29.0.zip",
    ],
)

http_archive(
    name = "bazel_gazelle",
    sha256 = "de69a09dc70417580aabf20a28619bb3ef60d038470c7cf8442fafcf627c21cb",
    urls = [
        "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz",
        "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz",
    ],
)

load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")

go_rules_dependencies()

go_register_toolchains(version = "1.17.2")

gazelle_dependencies()

Com as dependências definidas e configuradas no WORKSPACE, vamos para o arquivo BUILD.

Nesse arquivo fazer o load da função gazelle, definir o gazelle:prefix com o nome do nosso módulo e chamar a função que carregamos no inicio.

load("@bazel_gazelle//:def.bzl", "gazelle")

# gazelle:prefix github.com/aprendagolang/gorilla
gazelle(name = "gazelle")

Agora vamos executar o comando bazel run gazelle e ver a mágica acontecer.

Se você olhar na pasta handlers, vai ver que um arquivo BUILD.bazel foi criado com uma função go_library toda configurada, com os arquivos do package e suas dependências.

Já no arquivo BUILD que havíamos criado no inicio, o gazelle adicionou uma função go_library para configurar nosso main.go e suas dependências, e uma função go_binary, onde aponta para a go_library que contém o ponto de entrada do nosso programa, ou seja, a função main.

Antes de executar nossa API, vamos executar um comando do Bazel para gerar/atualizar um arquivo deps.bzl com todas as dependências externas do nosso projeto.

bazel run //:gazelle -- update-repos -from_file=go.mod -to_macro=deps.bzl%go_dependencies

Se você olhar o arquivo WORKSPACE, verá que foram adicionadas as linhas:

load("//:deps.bzl", "go_dependencies")

# gazelle:repository_macro deps.bzl%go_dependencies
go_dependencies()

Agora que temos tudo configurado, podemos iniciar nossa API com o comando bazel run gorilla. A primeira execução pode ser um pouco mais lenta, pois ele está gerando o cache das etapas de build. Mas se você parar a API e executar o comando novamente, verá que o build é praticamente instantâneo.

Na próxima parte, vamos adicionar as regras do Bazel para gerar uma imagem docker e subir para um registry. Então se inscreva em nossa newsletter para não ficar de fora!

Deixem suas dúvidas nos comentários.

Até a próxima!


Subscreva

Fique por dentro de tudo o que acontece no mundo Go.

Um comentário sobre “Buildando aplicações com Bazel (parte 1)

Deixe uma resposta