Microsserviços em Go: da ideia à produção
Go (Golang) conquistou a reputação de ser a linguagem ideal para construir arquitetura de microsserviços. Graças ao suporte embutido para concorrência, compilação rápida, consumo mínimo de memória e sintaxe simples, o Go permite criar serviços leves e de alto desempenho. Neste artigo, vamos analisar os princípios-chave para construir microsserviços em Go, explorar exemplos práticos e melhores práticas que ajudarão você a evitar erros comuns.
Por que Go é a escolha ideal para microsserviços?
Antes de mergulharmos na arquitetura, vamos entender por que o Go se tornou o padrão de facto para microsserviços:
- Início rápido: Go compila em um único arquivo binário, o que simplifica a implantação e o escalonamento.
- Concorrência embutida: goroutines e channels permitem processar milhares de requisições de forma eficiente, sem threads complexas.
- Consumo mínimo de memória: cada microsserviço em Go consome muito menos recursos do que equivalentes em Java ou Python.
- Facilidade de manutenção: a tipagem estática e o compilador rigoroso reduzem a quantidade de erros em tempo de execução.
Arquitetura de microsserviço em Go: template básico
Vamos considerar a estrutura típica de um microsserviço em Go. Como exemplo, criaremos um serviço simples de gerenciamento de usuários.
Estrutura do projeto
user-service/├── cmd/│ └── server/│ └── main.go├── internal/│ ├── handler/│ │ └── user.go│ ├── service/│ │ └── user.go│ ├── repository/│ │ └── postgres.go│ └── model/│ └── user.go├── pkg/│ └── middleware/│ └── logging.go├── config/│ └── config.go├── go.mod└── DockerfileEste template segue os princípios da Clean Architecture: as dependências apontam para dentro, e a lógica de negócio é isolada da infraestrutura.
Exemplo do arquivo principal (cmd/server/main.go)
package main
import ( "log" "net/http" "os" "user-service/internal/handler" "user-service/internal/repository" "user-service/internal/service")
func main() { // Inicialização de dependências db, err := repository.NewPostgresDB("postgres://user:pass@localhost:5432/users") if err != nil { log.Fatal(err) } defer db.Close()
userRepo := repository.NewUserRepository(db) userSvc := service.NewUserService(userRepo) userHandler := handler.NewUserHandler(userSvc)
// Configuração de rotas mux := http.NewServeMux() mux.HandleFunc("GET /users/{id}", userHandler.GetByID) mux.HandleFunc("POST /users", userHandler.Create) mux.HandleFunc("PUT /users/{id}", userHandler.Update)
// Inicialização do servidor port := os.Getenv("PORT") if port == "" { port = "8080" } log.Printf("Server starting on :%s", port) log.Fatal(http.ListenAndServe(":"+port, mux))}Implementação das camadas do microsserviço
Vamos dividir o código em três camadas principais: handler (transporte), service (lógica de negócio) e repository (acesso a dados).
Camada handler: processamento de requisições HTTP
// internal/handler/user.gopackage handler
import ( "encoding/json" "net/http" "user-service/internal/model" "user-service/internal/service")
type UserHandler struct { svc *service.UserService}
func NewUserHandler(svc *service.UserService) *UserHandler { return &UserHandler{svc: svc}}
func (h *UserHandler) GetByID(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") user, err := h.svc.GetByID(r.Context(), id) if err != nil { http.Error(w, err.Error(), http.StatusNotFound) return } json.NewEncoder(w).Encode(user)}
func (h *UserHandler) Create(w http.ResponseWriter, r *http