Projeto Completo (Parte 2)

Lição 30: Projeto Completo (Parte 2)

Analogia do Mundo Real

Na lição anterior completamos a análise de requisitos, design de estrutura de diretórios, modelos de dados e lógica de negócio principal do "Sistema de Gerenciamento de Tarefas TaskFlow" — como construir uma casa com a fundação lançada e a estrutura erguida. Nesta lição, completaremos o acabamento e entrega:

Após esta lição, você terá um projeto Go completo implantável, testável e documentado.


Revisão do Projeto

Na lição anterior construímos o esqueleto principal do TaskFlow. Esta lição continua a refiná-lo. Se você não leu Lição 29: Projeto Completo (Parte 1), recomenda-se completar a primeira metade primeiro.

Estrutura final do projeto:

TEXT
taskflow/
├── cmd/
│   └── server/
│       └── main.go            # Ponto de entrada do programa
├── internal/
│   ├── model/
│   │   ├── task.go            # Modelo de dados
│   │   └── task_test.go       # Testes do modelo
│   ├── store/
│   │   ├── store.go           # Interface de armazenamento
│   │   ├── memory.go          # Implementação em memória (lição anterior)
│   │   └── sqlite.go          # Implementação SQLite (esta lição)
│   ├── service/
│   │   ├── task.go            # Lógica de negócio
│   │   └── task_test.go       # Testes unitários
│   ├── handler/
│   │   ├── task.go            # Handler HTTP
│   │   └── task_test.go       # Testes do handler
│   └── middleware/
│       ├── auth.go            # Middleware de autenticação
│       ├── logging.go         # Middleware de logging
│       └── cors.go            # Middleware CORS
├── docs/
│   └── api.md                 # Documentação da API
├── Dockerfile                 # Empacotamento container
├── docker-compose.yml         # Configuração de orquestração
├── go.mod
├── go.sum
└── README.md

1. Conclusão da Camada REST API

1.1 Design de Rotas

Rotas da API projetadas seguindo convenções RESTful:

Método Caminho Descrição
GET /api/tasks Obter lista de tarefas
GET /api/tasks/{id} Obter tarefa única
POST /api/tasks Criar tarefa
PUT /api/tasks/{id} Atualizar tarefa
DELETE /api/tasks/{id} Excluir tarefa
GET /health Health check

1.2 Implementação Completa do Handler

GO
// internal/handler/task.go
package handler

import (
	"encoding/json"
	"errors"
	"net/http"
	"strconv"
	"strings"

	"taskflow/internal/model"
	"taskflow/internal/service"
)

// TaskHandler handler HTTP de tarefas
type TaskHandler struct {
	svc *service.TaskService
}

// NewTaskHandler cria uma instância de handler
func NewTaskHandler(svc *service.TaskService) *TaskHandler {
	return &TaskHandler{svc: svc}
}

// ErrorResponse formato de resposta de erro unificado
type ErrorResponse struct {
	Error   string `json:"error"`
	Code    int    `json:"code"`
	Message string `json:"message,omitempty"`
}

// SuccessResponse formato de resposta de sucesso unificado
type SuccessResponse struct {
	Data    interface{} `json:"data"`
	Message string      `json:"message,omitempty"`
}

// writeJSON escreve uma resposta JSON
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(data)
}

// writeError escreve uma resposta de erro
func writeError(w http.ResponseWriter, status int, msg string) {
	writeJSON(w, status, ErrorResponse{
		Error: msg,
		Code:  status,
	})
}

// extractID extrai o parâmetro ID do caminho da URL
func extractID(r *http.Request) (int, error) {
	parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
	if len(parts) < 3 {
		return 0, errors.New("parâmetro ID ausente")
	}
	return strconv.Atoi(parts[2])
}

// ListTasks trata GET /api/tasks
func (h *TaskHandler) ListTasks(w http.ResponseWriter, r *http.Request) {
	query := r.URL.Query()
	filter := service.TaskFilter{
		Status: model.TaskStatus(query.Get("status")),
	}

	page, _ := strconv.Atoi(query.Get("page"))
	pageSize, _ := strconv.Atoi(query.Get("page_size"))
	if page < 1 {
		page = 1
	}
	if pageSize < 1 || pageSize > 100 {
		pageSize = 20
	}

	tasks, total, err := h.svc.ListTasks(r.Context(), filter, page, pageSize)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "Falha na consulta: "+err.Error())
		return
	}

	writeJSON(w, http.StatusOK, map[string]interface{}{
		"data":      tasks,
		"total":     total,
		"page":      page,
		"page_size": pageSize,
	})
}

// GetTask trata GET /api/tasks/{id}
func (h *TaskHandler) GetTask(w http.ResponseWriter, r *http.Request) {
	id, err := extractID(r)
	if err != nil {
		writeError(w, http.StatusBadRequest, "ID de tarefa inválido")
		return
	}

	task, err := h.svc.GetTask(r.Context(), id)
	if err != nil {
		if errors.Is(err, service.ErrTaskNotFound) {
			writeError(w, http.StatusNotFound, "Tarefa não encontrada")
			return
		}
		writeError(w, http.StatusInternalServerError, "Falha na consulta")
		return
	}

	writeJSON(w, http.StatusOK, SuccessResponse{Data: task})
}

// CreateTask trata POST /api/tasks
func (h *TaskHandler) CreateTask(w http.ResponseWriter, r *http.Request) {
	var req model.CreateTaskRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "Formato de requisição inválido")
		return
	}

	task, err := h.svc.CreateTask(r.Context(), &req)
	if err != nil {
		if errors.Is(err, service.ErrValidation) {
			writeError(w, http.StatusBadRequest, err.Error())
			return
		}
		writeError(w, http.StatusInternalServerError, "Falha na criação")
		return
	}

	writeJSON(w, http.StatusCreated, SuccessResponse{
		Data:    task,
		Message: "Tarefa criada com sucesso",
	})
}

// UpdateTask trata PUT /api/tasks/{id}
func (h *TaskHandler) UpdateTask(w http.ResponseWriter, r *http.Request) {
	id, err := extractID(r)
	if err != nil {
		writeError(w, http.StatusBadRequest, "ID de tarefa inválido")
		return
	}

	var req model.UpdateTaskRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "Formato de requisição inválido")
		return
	}

	task, err := h.svc.UpdateTask(r.Context(), id, &req)
	if err != nil {
		switch {
		case errors.Is(err, service.ErrTaskNotFound):
			writeError(w, http.StatusNotFound, "Tarefa não encontrada")
		case errors.Is(err, service.ErrValidation):
			writeError(w, http.StatusBadRequest, err.Error())
		default:
			writeError(w, http.StatusInternalServerError, "Falha na atualização")
		}
		return
	}

	writeJSON(w, http.StatusOK, SuccessResponse{
		Data:    task,
		Message: "Tarefa atualizada com sucesso",
	})
}

// DeleteTask trata DELETE /api/tasks/{id}
func (h *TaskHandler) DeleteTask(w http.ResponseWriter, r *http.Request) {
	id, err := extractID(r)
	if err != nil {
		writeError(w, http.StatusBadRequest, "ID de tarefa inválido")
		return
	}

	if err := h.svc.DeleteTask(r.Context(), id); err != nil {
		if errors.Is(err, service.ErrTaskNotFound) {
			writeError(w, http.StatusNotFound, "Tarefa não encontrada")
			return
		}
		writeError(w, http.StatusInternalServerError, "Falha na exclusão")
		return
	}

	writeJSON(w, http.StatusOK, SuccessResponse{Message: "Tarefa excluída com sucesso"})
}

// HealthCheck endpoint de health check
func (h *TaskHandler) HealthCheck(w http.ResponseWriter, r *http.Request) {
	writeJSON(w, http.StatusOK, map[string]string{
		"status":  "ok",
		"service": "taskflow",
	})
}

1.3 Registro de Rotas e Inicialização do Servidor

GO
// cmd/server/main.go
package main

import (
	"context"
	"log/slog"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"

	"taskflow/internal/handler"
	"taskflow/internal/middleware"
	"taskflow/internal/service"
	"taskflow/internal/store"
)

func main() {
	logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
		Level: slog.LevelInfo,
	}))
	slog.SetDefault(logger)

	dbPath := getEnv("DB_PATH", "taskflow.db")
	st, err := store.NewSQLiteStore(dbPath)
	if err != nil {
		slog.Error("Falha na inicialização do banco de dados", "error", err)
		os.Exit(1)
	}
	defer st.Close()

	svc := service.NewTaskService(st)
	h := handler.NewTaskHandler(svc)

	mux := http.NewServeMux()
	mux.HandleFunc("GET /health", h.HealthCheck)
	mux.HandleFunc("GET /api/tasks", h.ListTasks)
	mux.HandleFunc("POST /api/tasks", h.CreateTask)
	mux.HandleFunc("GET /api/tasks/{id}", h.GetTask)
	mux.HandleFunc("PUT /api/tasks/{id}", h.UpdateTask)
	mux.HandleFunc("DELETE /api/tasks/{id}", h.DeleteTask)

	apiKey := getEnv("API_KEY", "dev-secret-key")
	handlerChain := middleware.Chain(
		mux,
		middleware.CORS(),
		middleware.Logging(),
		middleware.Auth(apiKey),
	)

	addr := ":" + getEnv("PORT", "8080")
	srv := &http.Server{
		Addr:         addr,
		Handler:      handlerChain,
		ReadTimeout:  10 * time.Second,
		WriteTimeout: 10 * time.Second,
		IdleTimeout:  60 * time.Second,
	}

	go func() {
		slog.Info("Serviço TaskFlow iniciado", "addr", addr)
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			slog.Error("Servidor saiu anormalmente", "error", err)
			os.Exit(1)
		}
	}()

	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit

	slog.Info("Sinal de encerramento recebido, encerrando graciosamente...")

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := srv.Shutdown(ctx); err != nil {
		slog.Error("Erro no encerramento do servidor", "error", err)
	}

	slog.Info("Serviço TaskFlow parado")
}

func getEnv(key, defaultVal string) string {
	if val := os.Getenv(key); val != "" {
		return val
	}
	return defaultVal
}
💡 Aprimoramento de Rotas Go 1.22+: A partir do Go 1.22, net/http suporta correspondência de método ("GET /path") e parâmetros de caminho ({id}), permitindo construir APIs RESTful sem bibliotecas de roteamento de terceiros.


2. Integração de Middleware

2.1 Padrão de Cadeia de Middleware

Middlewares são "interceptadores" no pipeline de processamento de requisições HTTP que podem executar lógica comum antes e depois das requisições chegarem ao handler:

TEXT
Requisição → [CORS] → [Logging] → [Auth] → [Handler] → Resposta
            ↓         ↓          ↓
        Controle    Registrar   Verificar
        cross-origin  duração   identidade

2.2 Infraestrutura de Middleware

GO
// internal/middleware/middleware.go
package middleware

import "net/http"

type Middleware func(http.Handler) http.Handler

func Chain(h http.Handler, middlewares ...Middleware) http.Handler {
	for i := len(middlewares) - 1; i >= 0; i-- {
		h = middlewares[i](h)
	}
	return h
}

2.3 Middleware CORS

GO
// internal/middleware/cors.go
package middleware

import "net/http"

func CORS() Middleware {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("Access-Control-Allow-Origin", "*")
			w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
			w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Request-ID")
			w.Header().Set("Access-Control-Max-Age", "86400")

			if r.Method == http.MethodOptions {
				w.WriteHeader(http.StatusNoContent)
				return
			}

			next.ServeHTTP(w, r)
		})
	}
}

2.4 Middleware de Logging

GO
// internal/middleware/logging.go
package middleware

import (
	"log/slog"
	"net/http"
	"time"
)

type responseWriter struct {
	http.ResponseWriter
	statusCode int
	written    int64
}

func newResponseWriter(w http.ResponseWriter) *responseWriter {
	return &responseWriter{
		ResponseWriter: w,
		statusCode:     http.StatusOK,
	}
}

func (rw *responseWriter) WriteHeader(code int) {
	rw.statusCode = code
	rw.ResponseWriter.WriteHeader(code)
}

func (rw *responseWriter) Write(b []byte) (int, error) {
	n, err := rw.ResponseWriter.Write(b)
	rw.written += int64(n)
	return n, err
}

func Logging() Middleware {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			start := time.Now()
			rw := newResponseWriter(w)
			next.ServeHTTP(rw, r)
			duration := time.Since(start)
			slog.Info("Requisição HTTP",
				"method", r.Method,
				"path", r.URL.Path,
				"status", rw.statusCode,
				"duration_ms", duration.Milliseconds(),
				"remote_addr", r.RemoteAddr,
				"user_agent", r.UserAgent(),
			)
		})
	}
}

2.5 Middleware de Autenticação

GO
// internal/middleware/auth.go
package middleware

import (
	"net/http"
	"strings"
)

func Auth(apiKey string) Middleware {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.URL.Path == "/health" {
				next.ServeHTTP(w, r)
				return
			}

			authHeader := r.Header.Get("Authorization")
			if authHeader == "" {
				w.Header().Set("WWW-Authenticate", `Bearer realm="taskflow"`)
				http.Error(w, `{"error":"Autenticação ausente","code":401}`, http.StatusUnauthorized)
				return
			}

			parts := strings.SplitN(authHeader, " ", 2)
			if len(parts) != 2 || parts[0] != "Bearer" {
				http.Error(w, `{"error":"Formato de autenticação inválido","code":401}`, http.StatusUnauthorized)
				return
			}

			if parts[1] != apiKey {
				http.Error(w, `{"error":"Falha na autenticação: API key inválida","code":403}`, http.StatusForbidden)
				return
			}

			next.ServeHTTP(w, r)
		})
	}
}

3. Integração com Banco de Dados (SQLite)

3.1 Por que SQLite?

Recurso SQLite MySQL/PostgreSQL
Instalação Zero config, arquivo único Requer serviço separado
Caso de uso Projetos pequenos-médios, embarcado Ambientes de grande produção
Concorrência Leitura concorrente, escrita serial Alta concorrência
Migração Auto-implementação necessária Ferramentas ricas
💡 Para projetos de aprendizado e aplicações pequenas-médias, SQLite é a melhor escolha inicial. Quando o tráfego crescer, você pode mudar seamlessmente para PostgreSQL.

3.2 Implementação de Armazenamento SQLite

GO
// internal/store/sqlite.go
package store

import (
	"context"
	"database/sql"
	"fmt"
	"time"

	_ "github.com/mattn/go-sqlite3"

	"taskflow/internal/model"
)

type SQLiteStore struct {
	db *sql.DB
}

func NewSQLiteStore(dbPath string) (*SQLiteStore, error) {
	db, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL&_busy_timeout=5000")
	if err != nil {
		return nil, fmt.Errorf("falha ao abrir banco de dados: %w", err)
	}

	if err := db.Ping(); err != nil {
		return nil, fmt.Errorf("falha na conexão com banco de dados: %w", err)
	}

	db.SetMaxOpenConns(1)
	db.SetMaxIdleConns(1)
	db.SetConnMaxLifetime(0)

	if err := migrate(db); err != nil {
		return nil, fmt.Errorf("falha na migração do banco de dados: %w", err)
	}

	return &SQLiteStore{db: db}, nil
}

func migrate(db *sql.DB) error {
	query := `
	CREATE TABLE IF NOT EXISTS tasks (
		id          INTEGER PRIMARY KEY AUTOINCREMENT,
		title       TEXT    NOT NULL,
		description TEXT    NOT NULL DEFAULT '',
		status      TEXT    NOT NULL DEFAULT 'pending',
		priority    TEXT    NOT NULL DEFAULT 'medium',
		due_date    DATETIME,
		created_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
		updated_at  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
	);

	CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
	CREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(priority);
	CREATE INDEX IF NOT EXISTS idx_tasks_due_date ON tasks(due_date);
	`
	_, err := db.Exec(query)
	return err
}

func (s *SQLiteStore) Close() error {
	return s.db.Close()
}

func (s *SQLiteStore) Create(ctx context.Context, task *model.Task) error {
	query := `INSERT INTO tasks (title, description, status, priority, due_date, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)`
	now := time.Now()
	result, err := s.db.ExecContext(ctx, query, task.Title, task.Description, task.Status, task.Priority, task.DueDate, now, now)
	if err != nil {
		return fmt.Errorf("falha ao inserir tarefa: %w", err)
	}
	id, err := result.LastInsertId()
	if err != nil {
		return fmt.Errorf("falha ao obter ID de inserção: %w", err)
	}
	task.ID = int(id)
	task.CreatedAt = now
	task.UpdatedAt = now
	return nil
}

func (s *SQLiteStore) GetByID(ctx context.Context, id int) (*model.Task, error) {
	query := `SELECT id, title, description, status, priority, due_date, created_at, updated_at FROM tasks WHERE id = ?`
	var task model.Task
	var dueDate sql.NullTime
	err := s.db.QueryRowContext(ctx, query, id).Scan(&task.ID, &task.Title, &task.Description, &task.Status, &task.Priority, &dueDate, &task.CreatedAt, &task.UpdatedAt)
	if err == sql.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("falha ao consultar tarefa: %w", err)
	}
	if dueDate.Valid {
		task.DueDate = &dueDate.Time
	}
	return &task, nil
}

type TaskFilter struct {
	Status   model.TaskStatus
	Priority model.TaskPriority
}

3.3 Interface de Armazenamento Unificada

GO
// internal/store/store.go
package store

import (
	"context"
	"taskflow/internal/model"
)

type TaskStore interface {
	Create(ctx context.Context, task *model.Task) error
	GetByID(ctx context.Context, id int) (*model.Task, error)
	List(ctx context.Context, filter TaskFilter, page, pageSize int) ([]model.Task, int, error)
	Update(ctx context.Context, task *model.Task) error
	Delete(ctx context.Context, id int) error
	Close() error
}
💡 Injeção de Dependência: A camada de negócio depende apenas da interface TaskStore, sem se importar se o armazenamento subjacente é SQLite ou memória. Injete mock para teste, implementação real para produção.


4. Testes de Integração

Testes de integração verificam se múltiplos componentes trabalham juntos corretamente, usando httptest para simular requisições HTTP reais.

4.1 Executando Testes de Integração

BASH
# Executar todos os testes
go test ./... -v

# Executar apenas testes de integração
go test -run TestIntegration -v

# Ver cobertura
go test ./... -cover -coverprofile=coverage.out

# Gerar relatório de cobertura HTML
go tool cover -html=coverage.out -o coverage.html

# Detecção de race condition
go test -race ./...

5. Documentação da API (Swagger/OpenAPI)

OpenAPI é a especificação padrão para descrever APIs RESTful. Para projetos Go, recomenda-se usar swaggo/swag para gerar documentação automaticamente a partir de comentários no código.

BASH
# Instalar ferramenta swag
go install github.com/swaggo/swag/cmd/swag@latest

# Gerar documentação no diretório raiz do projeto
swag init -g cmd/server/main.go

6. Diretrizes de Escrita do README

Um excelente README deve incluir: título, funcionalidades, início rápido, estrutura do projeto, testes, implantação e licença.

💡 Princípio de Escrita do README: Faça os outros rodarem em 30 segundos primeiro, depois expanda os detalhes gradualmente.


7. Empacotamento Dockerfile e Lançamento

7.1 Build Multi-Stage

DOCKERFILE
FROM golang:1.22-alpine AS builder
RUN apk add --no-cache gcc musl-dev
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=1 GOOS=linux go build -ldflags="-s -w" -o /app/taskflow ./cmd/server/main.go

FROM alpine:3.19
RUN apk add --no-cache ca-certificates tzdata
RUN adduser -D -g '' appuser
WORKDIR /app
COPY --from=builder /app/taskflow .
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 CMD wget -qO- http://localhost:8080/health || exit 1
ENTRYPOINT ["./taskflow"]

7.2 Docker Compose

YAML
version: "3.8"
services:
  taskflow:
    build: .
    ports:
      - "8080:8080"
    environment:
      - PORT=8080
      - DB_PATH=/data/taskflow.db
      - API_KEY=${API_KEY:-change-me-in-production}
      - TZ=America/Sao_Paulo
    volumes:
      - taskflow-data:/data
    restart: unless-stopped

volumes:
  taskflow-data:

8. Guia de Implantação

8.1 Checklist de Implantação

TEXT
✅ Verificações de Segurança
   □ API_KEY de produção alterada para senha forte
   □ Configuração CORS restringe domínios de origem permitidos
   □ Logs não contêm informações sensíveis
   □ Usando HTTPS (configurar proxy reverso)

✅ Verificações de Desempenho
   □ Pool de conexões do banco de dados configurado
   □ Timeouts HTTP definidos (Read/Write/Idle)
   □ Tamanho do corpo da requisição limitado

✅ Verificações de Confiabilidade
   □ Endpoint de health check disponível
   □ Lógica de encerramento gracioso implementada
   □ Formato de log é JSON estruturado

❓ Perguntas Frequentes

P1: SQLite reporta "database is locked" no Docker?

R: SQLite tem limitações em escritas concorrentes. Defina db.SetMaxOpenConns(1), habilite modo WAL com PRAGMA journal_mode=WAL e adicione ?_busy_timeout=5000 à string de conexão.

P2: A ordem de execução do middleware importa?

R: Sim. Ordem recomendada: CORS → Logging → Auth → Handler de negócio. CORS deve ser tratado primeiro pois requisições preflight não carregam informações de autenticação.

P3: Como gerenciar API keys em produção?

R: Nunca hardcode chaves. Use variáveis de ambiente, arquivos .env ou Docker Secrets.

P4: Devo usar SQLite ou PostgreSQL?

R: Para aprendizado e baixo tráfego, use SQLite. Para alta concorrência ou múltiplos serviços, use PostgreSQL.


📖 Resumo do Curso

Parabéns por completar todas as 30 lições do tutorial de Go!

Fase 1 — Fundamentos Go (Lições 1-6)

Variáveis e tipos, fluxo de controle, funções, arrays/slices, maps.

Fase 2 — Structs e Interfaces (Lições 7-12)

Structs, métodos, interfaces, tratamento de erros, pacotes e módulos.

Fase 3 — Programação Concorrente (Lições 13-18)

Goroutines, channels, select, pacote sync, context.

Fase 4 — Biblioteca Padrão (Lições 19-24)

Strings, arquivos, JSON, HTTP, testes, regex.

Fase 5 — Projetos Abrangentes (Lições 25-30)

CLI, REST API, banco de dados, deploy, projeto completo.

Competências Adquiridas

TEXT
✅ Sintaxe básica e sistema de tipos Go
✅ Programação orientada a objetos (structs + interfaces)
✅ Programação concorrente (Goroutine + Channel + sync)
✅ Pacotes principais da biblioteca padrão
✅ Design e desenvolvimento de API RESTful
✅ Operações com banco de dados
✅ Desenvolvimento orientado a testes
✅ Engenharia de projeto
✅ Implantação containerizada

📝 Sugestões de Aprendizado Adicional

Direções Avançadas

Direção Recursos Recomendados
Framework Web Gin, Echo
ORM GORM, sqlx
Microsserviços go-kit, Kratos
Config Viper
Logging zap, zerolog

Livros Recomendados

  1. "The Go Programming Language" — Donovan & Kernighan
  2. "Go in Action" — William Kennedy
  3. "Concurrency in Go" — Katherine Cox-Buday
  4. "100 Go Mistakes and How to Avoid Them" — Teiva Harsanyi

Recursos Online


🎉 Conclusão

"Less is more" — Filosofia de design do Go

30 lições, de fmt.Println("Hello, World!") a construir um serviço RESTful API implantável, você completou toda a jornada de aprendizado da linguagem Go.

O encanto do Go está em sua simplicidade — ele não te dá muitas escolhas, mas cada escolha é cuidadosamente considerada. Programação é um ofício, e a melhor maneira de aprender é escrever código. Digite todos os exemplos das 30 lições, complete todos os exercícios, e depois encontre um pequeno projeto real para praticar.

TEXT
  ╔══════════════════════════════════════════════════╗
  ║   Parabéns por completar as 30 lições            ║
  ║   do tutorial de Go!                             ║
  ║   Você agora tem a base para desenvolver         ║
  ║   aplicações Go de nível produção.               ║
  ║   Vá construir algo incrível!                    ║
  ╚══════════════════════════════════════════════════╝

Próxima Lição

Esta é a última lição. Se você gostaria de revisitar o ponto de partida do curso, volte para Lição 1: Introdução ao Go.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%