Construindo Aplicações Robustas e Escaláveis
Uma base de código rastreada em controle de versão, muitas implantações
git init
git add .
git commit -m "Initial commit"
Declare e isole dependências
// go.mod
module myapp
go 1.22
require (
github.com/labstack/echo/v4 v4.6.3
)
go mod tidy
Armazene a configuração no ambiente
package main
import (
"fmt"
"os"
)
func main() {
dbURL := os.Getenv("DATABASE_URL")
fmt.Println("Database URL:", dbURL)
}
export DATABASE_URL="postgres://user:password@localhost/db"
go run main.go
Trate serviços de apoio como recursos anexados
package main
import (
"fmt"
"os"
)
func main() {
redisURL := os.Getenv("REDIS_URL")
fmt.Println("Redis URL:", redisURL)
}
export REDIS_URL="redis://localhost:6379"
go run main.go
Separe estritamente os estágios de construção e execução
# Build stage
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# Run stage
FROM alpine:latest
WORKDIR /root/
COPY --from=builder /app/myapp .
CMD ["./myapp"]
Execute a aplicação como um ou mais processos sem estado
package main
import (
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
e.GET("/ping", func(c echo.Context) error {
return c.String(200, "pong")
})
e.Start(":8080")
}
Exporte serviços via ligação de porta
package main
import (
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
e.GET("/ping", func(c echo.Context) error {
return c.String(200, "pong")
})
e.Start(":8080")
}
Escale por meio do modelo de processo
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:latest
ports:
- containerPort: 8080
Maximize a robustez com inicialização rápida e encerramento gracioso
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"time"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
e.GET("/ping", func(c echo.Context) error {
return c.String(200, "pong")
})
go func() {
if err := e.Start(":8080"); err != nil && err != http.ErrServerClosed {
log.Fatalf("shutting down the server: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := e.Shutdown(ctx); err != nil {
log.Fatal("Server forced to shutdown:", err)
}
log.Println("Server exiting")
}
Mantenha o desenvolvimento, o teste e a produção o mais semelhante possível
# Dockerfile para desenvolvimento e produção
FROM golang:1.22
WORKDIR /app
COPY . .
RUN go build -o myapp
CMD ["./myapp"]
Trate logs como fluxos de eventos
package main
import (
"log"
"net/http"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
e.GET("/ping", func(c echo.Context) error {
log.Println("Received request for /ping")
return c.String(200, "pong")
})
log.Fatal(e.Start(":8080"))
}
Execute tarefas de administração/gerenciamento como processos únicos
package main
import (
"database/sql"
"fmt"
_ "github.com/lib/pq"
)
func main() {
db, err := sql.Open("postgres", "user=username dbname=mydb sslmode=disable")
if err != nil {
panic(err)
}
defer db.Close()
_, err = db.Exec("ALTER TABLE mytable ADD COLUMN newcolumn TEXT")
if err != nil {
panic(err)
}
fmt.Println("Migration completed successfully")
}
Abordagem de desenvolvimento onde os testes são escritos antes do código funcional
Vamos criar uma função simples para adicionar dois números e escrever testes para ela.
package main
import "fmt"
// Add function
func Add(a, b int) int {
return a + b
}
func main() {
fmt.Println(Add(2, 3))
}
package main
import "testing"
// TestAdd function
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("Add(2, 3) = %d; want 5", result)
}
}
Usando Table-Driven Tests para melhorar a legibilidade e manutenção dos testes
package main
import "testing"
// TestAdd function with Table-Driven Tests
func TestAdd(t *testing.T) {
var tests = []struct {
a, b, expected int
}{
{1, 1, 2},
{2, 3, 5},
{10, 20, 30},
}
for _, tt := range tests {
testname := fmt.Sprintf("%d+%d", tt.a, tt.b)
t.Run(testname, func(t *testing.T) {
result := Add(tt.a, tt.b)
if result != tt.expected {
t.Errorf("got %d, want %d", result, tt.expected)
}
})
}
}
Leia mais sobre TDD em Go no meu artigo.