🐹 Golang Desde Cero - Guía Completa

Aprende Go desde los fundamentos hasta conceptos avanzados

"Go is an open source programming language supported by Google that makes it easy to build simple, reliable, and efficient software."
Explorar Temas

📖 Temas del Curso

Haz clic en cualquier tema para ver el contenido detallado

📌 Introducción a Go

¿Qué es Go? Go (también conocido como Golang) es un lenguaje de programación de código abierto desarrollado por Google en 2007 por Robert Griesemer, Rob Pike y Ken Thompson. Fue diseñado para ser simple, rápido y eficiente en el manejo de concurrencia.

Características principales:

  • Rápido: Compilación rápida y ejecución eficiente
  • Simple: Sintaxis limpia y fácil de aprender
  • Concurrente: Goroutines y channels para concurrencia nativa
  • Tipado estático: Detección de errores en tiempo de compilación

Historia de Go:

Año Evento
2007 Diseño inicial por Pike, Thompson y Griesemer
2009 Anuncio oficial y lanzamiento
2012 Go 1.0 - Promesa de compatibilidad
2024 Go 1.22+ con mejoras continuas
💡 Dato curioso: El mascot de Go es el Gopher, diseñado por Renée French. Los gophers son roedores relacionados con las ardillas.
← Volver a temas

🔧 Instalación y Configuración

En Windows:

  1. Descarga Go desde go.dev/dl
  2. Ejecuta el instalador MSI
  3. Acepta la instalación en C:\Go (por defecto)
  4. Abre una nueva terminal y verifica: go version

En macOS:

# Con Homebrew
brew install go

# Verificar instalación
go version

# Configurar GOPATH (opcional en Go 1.16+)
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin

En Linux (Ubuntu/Debian):

# Descargar última versión
wget https://go.dev/dl/go1.22.0.linux-amd64.tar.gz

# Extraer en /usr/local
sudo tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz

# Agregar al PATH (en ~/.bashrc o ~/.zshrc)
export PATH=$PATH:/usr/local/go/bin

# Recargar shell
source ~/.bashrc

# Verificar
go version

Configuración del entorno:

# Ver configuración actual
go env

# Establecer GOPROXY
go env -w GOPROXY=https://proxy.golang.org,direct

# Habilitar Go modules
go env -w GO111MODULE=on

# Verificar instalación
go run hello.go

Editor recomendado - VS Code:

# Instalar extensión Go en VS Code
# La extensión instala automáticamente:
# - gopls (language server)
# - delve (debugger)
# - golangci-lint (linter)
# - gotests (generador de tests)
← Volver a temas

📦 Variables y Tipos de Datos

Declaración de variables:

package main

import "fmt"

func main() {
    // Declaración explícita
    var nombre string = "Juan"
    var edad int = 30
    
    // Inferencia de tipo (short declaration)
    pais := "España"
    activo := true
    
    // Múltiples variables
    var x, y, z int = 1, 2, 3
    a, b := 10, 20
    
    // Variable sin valor inicial (zero value)
    var contador int      // 0
    var precio float64    // 0.0
    var nombre2 string    // ""
    var estado bool       // false
    
    fmt.Println(nombre, edad, pais, activo)
}

Tipos de datos básicos:

// Strings
var texto string = "Hola Mundo"
multilinea := `Esto es un
texto de múltiples
líneas`

// Números enteros
var entero int = 42
var entero8 int8 = 127
var entero32 int32 = 1000
var entero64 int64 = 9223372036854775807
var sinSigno uint = 100

// Números flotantes
var decimal float32 = 3.14
var doble float64 = 3.14159265359

// Booleanos
var verdadero bool = true
var falso bool = false

// Byte y rune
var byteVal byte = 'A'      // alias de uint8
var runeVal rune = 'ñ'      // alias de int32

Constantes:

const PI = 3.1416
const (
    Enero = 1
    Febrero = 2
    Marzo = 3
)

// iota para enumeraciones
const (
    Domingo = iota  // 0
    Lunes           // 1
    Martes          // 2
    Miercoles       // 3
    Jueves          // 4
    Viernes         // 5
    Sabado          // 6
)

Conversión de tipos:

var entero int = 42
var decimal float64 = float64(entero)

var numero int64 = 100
var convertido int = int(numero)

// String a int
import "strconv"
num, _ := strconv.Atoi("42")

// int a String
texto := strconv.Itoa(42)
← Volver a temas

🔷 Funciones y Control de Flujo

Declaración de funciones:

package main

import "fmt"

// Función básica
func saludar() {
    fmt.Println("Hola!")
}

// Función con parámetros
func saludarPersona(nombre string) {
    fmt.Println("Hola", nombre)
}

// Función con valor por defecto (no existe en Go)
// Usar patrón alternativo
func saludarOpcional(nombre ...string) {
    n := "Invitado"
    if len(nombre) > 0 {
        n = nombre[0]
    }
    fmt.Println("Hola", n)
}

// Múltiples retornos
func dividir(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("división por cero")
    }
    return a / b, nil
}

// Retornos nombrados
func operar(a, b int) (suma, resta int) {
    suma = a + b
    resta = a - b
    return  // naked return
}

Control de flujo - if/else:

// if básico
if edad >= 18 {
    fmt.Println("Mayor de edad")
}

// if con inicialización
if edad := obtenerEdad(); edad >= 18 {
    fmt.Println("Mayor de edad")
}

// if-else
if nota >= 90 {
    fmt.Println("Excelente")
} else if nota >= 70 {
    fmt.Println("Aprobado")
} else {
    fmt.Println("Reprobado")
}

Control de flujo - switch:

// switch básico
dia := "lunes"
switch dia {
case "lunes":
    fmt.Println("Inicio de semana")
case "viernes":
    fmt.Println("Fin de semana")
default:
    fmt.Println("Día normal")
}

// switch sin condición (como if-else)
switch {
case nota >= 90:
    fmt.Println("A")
case nota >= 80:
    fmt.Println("B")
default:
    fmt.Println("C")
}

// switch con caída (fallthrough)
switch nivel {
case 1:
    fmt.Println("Nivel 1")
    fallthrough
case 2:
    fmt.Println("Nivel 1 y 2")
}

Control de flujo - for:

// for tradicional
for i := 0; i < 10; i++ {
    fmt.Println(i)
}

// for como while
i := 0
for i < 10 {
    fmt.Println(i)
    i++
}

// for infinito
for {
    // break para salir
    if condicion {
        break
    }
}

// for range (arrays, slices, maps, strings)
numeros := []int{1, 2, 3, 4, 5}
for indice, valor := range numeros {
    fmt.Println(indice, valor)
}

// Solo valor
for _, valor := range numeros {
    fmt.Println(valor)
}

// Solo índice
for indice := range numeros {
    fmt.Println(indice)
}
💡 Tip: Go solo tiene for para bucles. No existe while o do-while, pero for puede comportarse como ellos.
← Volver a temas

🏗️ Structs y Métodos

Definición de structs:

package main

import "fmt"

// Definición de struct
type Persona struct {
    Nombre string
    Edad   int
    Email  string
}

// Crear instancia
func main() {
    // Forma 1
    p1 := Persona{"Juan", 30, "juan@email.com"}
    
    // Forma 2 (con nombres)
    p2 := Persona{
        Nombre: "Maria",
        Edad:   25,
        Email:  "maria@email.com",
    }
    
    // Forma 3 (zero values)
    var p3 Persona
    p3.Nombre = "Carlos"
    
    // Acceder a campos
    fmt.Println(p1.Nombre)
    fmt.Println(p2.Edad)
    
    // Modificar campos
    p1.Edad = 31
    
    fmt.Println(p1, p2, p3)
}

Métodos:

type Persona struct {
    Nombre string
    Edad   int
}

// Método con receptor por valor
func (p Persona) Saludar() string {
    return "Hola, soy " + p.Nombre
}

// Método con receptor por pointer (modifica el original)
func (p *Persona) CumplirAnios() {
    p.Edad++
}

// Método con parámetros
func (p Persona) Presentarse(lugar string) string {
    return fmt.Sprintf("Soy %s y estoy en %s", p.Nombre, lugar)
}

func main() {
    juan := Persona{"Juan", 30}
    
    fmt.Println(juan.Saludar())
    juan.CumplirAnios()  // Ahora tiene 31
    fmt.Println(juan.Presentarse("Madrid"))
}

Structs anidados (Composición):

type Direccion struct {
    Calle  string
    Ciudad string
    CP     string
}

type Empleado struct {
    Persona    // Embedding (composición)
    Direccion  // Embedding
    Puesto     string
    Salario    float64
}

func main() {
    emp := Empleado{
        Persona: Persona{
            Nombre: "Ana",
            Edad:   28,
        },
        Direccion: Direccion{
            Calle:  "Calle Mayor",
            Ciudad: "Madrid",
            CP:     "28001",
        },
        Puesto:  "Desarrolladora",
        Salario: 45000,
    }
    
    // Acceso directo a campos embebidos
    fmt.Println(emp.Nombre)  // Ana
    fmt.Println(emp.Ciudad)  // Madrid
}
💡 Nota: Go no tiene herencia de clases. Usa composición (embedding) para reutilizar código.
← Volver a temas

🔌 Interfaces

Definición de interfaces:

package main

import "fmt"

// Interface define un conjunto de métodos
type Hablador interface {
    Hablar() string
}

type Escuchador interface {
    Escuchar() string
}

// Interface que combina otras dos
type Comunicador interface {
    Hablador
    Escuchador
}

// Implementación
type Persona struct {
    Nombre string
}

func (p Persona) Hablar() string {
    return "Hola, soy " + p.Nombre
}

func (p Persona) Escuchar() string {
    return p.Nombre + " está escuchando"
}

// Función que acepta interface
func Interactuar(c Comunicador) {
    fmt.Println(c.Hablar())
    fmt.Println(c.Escuchar())
}

func main() {
    juan := Persona{"Juan"}
    Interactuar(juan)
}

Interface vacía (any):

// interface{} acepta cualquier tipo (Go < 1.18)
func imprimir(v interface{}) {
    fmt.Println(v)
}

// any es alias de interface{} (Go 1.18+)
func imprimir2(v any) {
    fmt.Println(v)
}

// Type assertion
func procesar(v interface{}) {
    // Afirmación de tipo
    str, ok := v.(string)
    if ok {
        fmt.Println("Es string:", str)
        return
    }
    
    num, ok := v.(int)
    if ok {
        fmt.Println("Es int:", num)
        return
    }
}

// Type switch
func describir(v interface{}) {
    switch t := v.(type) {
    case int:
        fmt.Println("Entero:", t)
    case string:
        fmt.Println("String:", t)
    case bool:
        fmt.Println("Booleano:", t)
    default:
        fmt.Printf("Tipo desconocido: %T\n", t)
    }
}

Common interfaces:

// Stringer - para representación string
type Stringer interface {
    String() string
}

func (p Persona) String() string {
    return fmt.Sprintf("Persona(%s, %d)", p.Nombre, p.Edad)
}

// Error - para manejo de errores
type error interface {
    Error() string
}

type MiError struct {
    Codigo int
    Mensaje string
}

func (e MiError) Error() string {
    return fmt.Sprintf("Error %d: %s", e.Codigo, e.Mensaje)
}

// Reader/Writer - para I/O
type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

Interfaces en la práctica:

// Interface para repositorios
type UsuarioRepository interface {
    GetById(id int) (*Usuario, error)
    GetAll() ([]*Usuario, error)
    Create(u *Usuario) error
    Update(u *Usuario) error
    Delete(id int) error
}

// Implementación con base de datos
type MySQLUsuarioRepository struct {
    db *sql.DB
}

func (r *MySQLUsuarioRepository) GetById(id int) (*Usuario, error) {
    // Implementación con SQL
}

// Implementación en memoria (para tests)
type MemoryUsuarioRepository struct {
    usuarios map[int]*Usuario
}

func (r *MemoryUsuarioRepository) GetById(id int) (*Usuario, error) {
    // Implementación simple
}
← Volver a temas

🔄 Concurrencia (Goroutines y Channels)

Goroutines:

package main

import (
    "fmt"
    "time"
)

func tarea(id int) {
    fmt.Printf("Tarea %d iniciada\n", id)
    time.Sleep(time.Second)
    fmt.Printf("Tarea %d completada\n", id)
}

func main() {
    // Lanzar goroutine
    go tarea(1)
    
    // Múltiples goroutines
    for i := 0; i < 5; i++ {
        go tarea(i)
    }
    
    // Esperar un poco (no recomendado en producción)
    time.Sleep(2 * time.Second)
}

WaitGroup:

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    var wg sync.WaitGroup
    
    for i := 0; i < 5; i++ {
        wg.Add(1)  // Incrementar contador
        
        go func(id int) {
            defer wg.Done()  // Decrementar al finalizar
            fmt.Printf("Tarea %d\n", id)
            time.Sleep(time.Second)
        }(i)
    }
    
    wg.Wait()  // Esperar todas las goroutines
    fmt.Println("Todas completadas")
}

Channels:

// Channel sin buffer
ch := make(chan int)

// Enviar valor
ch <- 42

// Recibir valor
valor := <-ch

// Channel con buffer
chBuffer := make(chan int, 10)
chBuffer <- 1
chBuffer <- 2

// Range sobre channel
for v := range ch {
    fmt.Println(v)
}

// Cerrar channel
close(ch)

Patrón worker pool:

func worker(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        fmt.Printf("Worker %d procesando %d\n", id, job)
        results <- job * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)
    
    // Iniciar workers
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }
    
    // Enviar trabajos
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)
    
    // Recibir resultados
    for r := 1; r <= 5; r++ {
        fmt.Println(<-results)
    }
}

Select:

func main() {
        ch1 := make(chan string)
        ch2 := make(chan string)
        
        go func() {
            time.Sleep(time.Second)
            ch1 <- "uno"
        }()
        
        go func() {
            time.Sleep(2 * time.Second)
            ch2 <- "dos"
        }()
        
        // Esperar el primero que llegue
        select {
        case msg1 := <-ch1:
            fmt.Println("Recibido:", msg1)
        case msg2 := <-ch2:
            fmt.Println("Recibido:", msg2)
        case <-time.After(time.Millisecond * 500):
            fmt.Println("Timeout")
        default:
            fmt.Println("Sin datos disponibles")
        }
    }
⚠️ Importante: No comuniques compartiendo memoria; comparte memoria comunicándote. Usa channels para sincronización segura.
← Volver a temas

⚠️ Manejo de Errores

Patrón básico de errores:

package main

import (
    "errors"
    "fmt"
)

// Error básico
func dividir(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("división por cero")
    }
    return a / b, nil
}

// Uso
resultado, err := dividir(10, 0)
if err != nil {
    fmt.Println("Error:", err)
    return
}
fmt.Println("Resultado:", resultado)

Errores personalizados:

// Tipo de error personalizado
type ErrorValidacion struct {
    Campo   string
    Mensaje string
}

func (e ErrorValidacion) Error() string {
    return fmt.Sprintf("Error en %s: %s", e.Campo, e.Mensaje)
}

// Uso
func validarEmail(email string) error {
    if email == "" {
        return ErrorValidacion{"email", "no puede estar vacío"}
    }
    if !strings.Contains(email, "@") {
        return ErrorValidacion{"email", "debe contener @"}
    }
    return nil
}

// Verificar tipo de error
err := validarEmail("")
if err != nil {
    var ve ErrorValidacion
    if errors.As(err, &ve) {
        fmt.Printf("Campo: %s, Mensaje: %s\n", ve.Campo, ve.Mensaje)
    }
}

Envoltura de errores (Go 1.13+):

import (
    "fmt"
    "errors"
)

func operar() error {
    err := dividir(10, 0)
    if err != nil {
        // Envolver error con contexto
        return fmt.Errorf("operar falló: %w", err)
    }
    return nil
}

// Desenvolver error
err := operar()
if err != nil {
    // Verificar error original
    if errors.Is(err, errors.New("división por cero")) {
        fmt.Println("Es error de división")
    }
    
    // Obtener error original
    var original error
    if errors.As(err, &original) {
        fmt.Println("Error original:", original)
    }
}

Patrón de errores sentinel:

// Errores sentinel (predefinidos)
var (
    ErrNotFound     = errors.New("no encontrado")
    ErrUnauthorized = errors.New("no autorizado")
    ErrInvalidInput = errors.New("entrada inválida")
)

func buscarUsuario(id int) (*Usuario, error) {
    if id <= 0 {
        return nil, ErrInvalidInput
    }
    // ... búsqueda
    return nil, ErrNotFound
}

// Uso
usuario, err := buscarUsuario(-1)
if errors.Is(err, ErrInvalidInput) {
    fmt.Println("ID inválido")
} else if errors.Is(err, ErrNotFound) {
    fmt.Println("Usuario no encontrado")
}
💡 Best Practice: En Go, los errores son valores. Siempre verifica los errores y proporcionalos con contexto significativo.
← Volver a temas

🌐 APIs HTTP/REST

Servidor HTTP básico:

package main

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

type Usuario struct {
    ID    int    `json:"id"`
    Nombre string `json:"nombre"`
    Email  string `json:"email"`
}

func main() {
    http.HandleFunc("/api/usuarios", obtenerUsuarios)
    http.HandleFunc("/api/usuarios/", obtenerUsuario)
    
    http.ListenAndServe(":8080", nil)
}

func obtenerUsuarios(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodGet {
        http.Error(w, "Método no permitido", http.StatusMethodNotAllowed)
        return
    }
    
    usuarios := []Usuario{
        {ID: 1, Nombre: "Juan", Email: "juan@email.com"},
        {ID: 2, Nombre: "Maria", Email: "maria@email.com"},
    }
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(usuarios)
}

func obtenerUsuario(w http.ResponseWriter, r *http.Request) {
    // Extraer ID del URL
    id := r.URL.Path[len("/api/usuarios/"):]
    
    usuario := Usuario{ID: 1, Nombre: "Juan", Email: "juan@email.com"}
    
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(usuario)
}

Router con Gorilla Mux:

import (
    "github.com/gorilla/mux"
)

func main() {
    r := mux.NewRouter()
    
    // Rutas
    r.HandleFunc("/api/usuarios", GetUsuarios).Methods("GET")
    r.HandleFunc("/api/usuarios/{id}", GetUsuario).Methods("GET")
    r.HandleFunc("/api/usuarios", CreateUsuario).Methods("POST")
    r.HandleFunc("/api/usuarios/{id}", UpdateUsuario).Methods("PUT")
    r.HandleFunc("/api/usuarios/{id}", DeleteUsuario).Methods("DELETE")
    
    // Middleware
    r.Use(loggingMiddleware)
    r.Use(authMiddleware)
    
    http.ListenAndServe(":8080", r)
}

func GetUsuario(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id := vars["id"]
    // ...
}

Middleware:

// Logging middleware
func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
    })
}

// CORS middleware
func corsMiddleware(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")
        
        if r.Method == "OPTIONS" {
            w.WriteHeader(http.StatusOK)
            return
        }
        
        next.ServeHTTP(w, r)
    })
}
💡 Tip: Para APIs más complejas, considera frameworks como Gin, Echo, o Fiber que ofrecen mejor rendimiento y características adicionales.
← Volver a temas

📡 gRPC y Protocol Buffers

Definición del servicio (.proto):

syntax = "proto3";

package usuario;

option go_package = "./proto";

// Mensajes
message UsuarioRequest {
    int32 id = 1;
}

message UsuarioResponse {
    int32 id = 1;
    string nombre = 2;
    string email = 3;
}

message UsuarioListResponse {
    repeated UsuarioResponse usuarios = 1;
}

// Servicio
service UsuarioService {
    rpc GetUsuario(UsuarioRequest) returns (UsuarioResponse);
    rpc ListUsuarios(UsuarioRequest) returns (UsuarioListResponse);
    rpc CreateUsuario(UsuarioRequest) returns (UsuarioResponse);
    rpc StreamUsuarios(UsuarioRequest) returns (stream UsuarioResponse);
}

Generar código Go:

# Instalar protoc y plugins
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest

# Generar código
protoc --go_out=. --go_opt=paths=source_relative \
       --go-grpc_out=. --go-grpc_opt=paths=source_relative \
       usuario.proto

Servidor gRPC:

type usuarioServer struct {
    pb.UnimplementedUsuarioServiceServer
}

func (s *usuarioServer) GetUsuario(ctx context.Context, req *pb.UsuarioRequest) (*pb.UsuarioResponse, error) {
    return &pb.UsuarioResponse{
        Id: req.Id,
        Nombre: "Juan",
        Email: "juan@email.com",
    }, nil
}

func main() {
    lis, _ := net.Listen("tcp", ":50051")
    s := grpc.NewServer()
    
    pb.RegisterUsuarioServiceServer(s, &usuarioServer{})
    
    log.Println("Servidor gRPC en :50051")
    s.Serve(lis)
}

Cliente gRPC:

func main() {
    conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
    defer conn.Close()
    
    client := pb.NewUsuarioServiceClient(conn)
    
    resp, _ := client.GetUsuario(context.Background(), &pb.UsuarioRequest{Id: 1})
    
    fmt.Printf("Usuario: %s (%s)\n", resp.Nombre, resp.Email)
}
💡 gRPC vs REST: gRPC es más rápido, usa HTTP/2, soporta streaming bidireccional y tiene contratos fuertes con protobuf. Ideal para microservicios internos.
← Volver a temas

🏗️ Microservicios

Arquitectura de microservicios:

/cmd
  /api-gateway
  /user-service
  /order-service
  /product-service
/internal
  /pkg
    /database
    /auth
    /middleware
  /user
    /handler
    /service
    /repository
  /order
    /handler
    /service
    /repository
/config
  /config.yaml
/docker
  /Dockerfile.api-gateway
  /Dockerfile.user-service

Configuración centralizada:

// config/config.go
type Config struct {
    Server   ServerConfig
    Database DatabaseConfig
    Redis    RedisConfig
}

type ServerConfig struct {
    Port string
    Host string
}

func LoadConfig() (*Config, error) {
    var config Config
    
    // Cargar desde archivo
    file, _ := os.Open("config/config.yaml")
    decoder := yaml.NewDecoder(file)
    decoder.Decode(&config)
    
    // O desde variables de entorno
    config.Server.Port = os.Getenv("SERVER_PORT")
    
    return &config, nil
}

Comunicación entre servicios:

// Cliente HTTP entre servicios
type UserServiceClient struct {
    baseURL string
    client  *http.Client
}

func NewUserServiceClient(baseURL string) *UserServiceClient {
    return &UserServiceClient{
        baseURL: baseURL,
        client:  &http.Client{Timeout: 10 * time.Second},
    }
}

func (c *UserServiceClient) GetUser(id int) (*User, error) {
    resp, err := c.client.Get(fmt.Sprintf("%s/users/%d", c.baseURL, id))
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    
    var user User
    json.NewDecoder(resp.Body).Decode(&user)
    return &user, nil
}

Circuit Breaker:

import "github.com/sony/gobreaker"

var cb *gobreaker.CircuitBreaker

func init() {
    cb = gobreaker.NewCircuitBreaker(gobreaker.Settings{
        Name:        "UserService",
        Timeout:     30 * time.Second,
        MaxRequests: 100,
        Interval:    60 * time.Second,
    })
}

func callUserService() (string, error) {
    return cb.Execute(func() (interface{}, error) {
        // Llamada al servicio
        return getUserFromService()
    })
}
💡 Patrones: API Gateway, Service Discovery, Circuit Breaker, CQRS, Event Sourcing son patrones comunes en arquitecturas de microservicios.
← Volver a temas

🛠️ CLI Tools con Cobra

Instalación y estructura:

# Instalar Cobra
go get -u github.com/spf13/cobra

# Estructura del proyecto
/cmd
  /root.go
  /server.go
  /config.go
  /version.go
/main.go

Comando raíz:

// cmd/root.go
package cmd

import (
    "fmt"
    "github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
    Use:   "mi-cli",
    Short: "Mi CLI tool",
    Long:  `Una herramienta CLI completa construida con Cobra`,
    PersistentPreRun: func(cmd *cobra.Command, args []string) {
        // Configuración inicial
    },
}

func Execute() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Println(err)
    }
}

Comandos personalizados:

// cmd/server.go
var (
    port     string
    host     string
    debug    bool
)

var serverCmd = &cobra.Command{
    Use:   "server",
    Short: "Iniciar el servidor",
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Printf("Iniciando servidor en %s:%s\n", host, port)
        if debug {
            fmt.Println("Modo debug activado")
        }
        // Iniciar servidor
    },
}

func init() {
    rootCmd.AddCommand(serverCmd)
    
    serverCmd.Flags().StringVarP(&port, "port", "p", "8080", "Puerto del servidor")
    serverCmd.Flags().StringVarP(&host, "host", "H", "localhost", "Host del servidor")
    serverCmd.Flags().BoolVarP(&debug, "debug", "d", false, "Modo debug")
}

Comando con subcomandos:

// cmd/config.go
var configCmd = &cobra.Command{
    Use:   "config",
    Short: "Gestionar configuración",
}

var configGetCmd = &cobra.Command{
    Use:   "get [key]",
    Short: "Obtener valor de configuración",
    Args:  cobra.ExactArgs(1),
    Run: func(cmd *cobra.Command, args []string) {
        key := args[0]
        value := getConfig(key)
        fmt.Printf("%s=%s\n", key, value)
    },
}

var configSetCmd = &cobra.Command{
    Use:   "set [key] [value]",
    Short: "Establecer valor de configuración",
    Args:  cobra.ExactArgs(2),
    Run: func(cmd *cobra.Command, args []string) {
        setConfig(args[0], args[1])
        fmt.Println("Configuración actualizada")
    },
}

func init() {
    rootCmd.AddCommand(configCmd)
    configCmd.AddCommand(configGetCmd)
    configCmd.AddCommand(configSetCmd)
}

Viper para configuración:

import "github.com/spf13/viper"

func initConfig() {
    viper.SetConfigName("config")
    viper.SetConfigType("yaml")
    viper.AddConfigPath(".")
    viper.AddConfigPath("$HOME/.mi-cli")
    
    viper.AutomaticEnv()
    
    if err := viper.ReadInConfig(); err != nil {
        fmt.Println("Error leyendo config:", err)
    }
}

// Uso
port := viper.GetInt("server.port")
debug := viper.GetBool("debug")
💡 Ejemplos famosos: kubectl, docker CLI, hugo, y github CLI están construidos con Cobra.
← Volver a temas

📚 Contenido del Curso

Módulo 1: Fundamentos

  • Introducción a Go
  • Instalación y configuración
  • Variables y tipos de datos
  • Funciones y control de flujo
Ir a temas →

Módulo 2: Intermedio

  • Structs y métodos
  • Interfaces
  • Concurrencia (goroutines, channels)
  • Manejo de errores
Ir a temas →

Módulo 3: Avanzado

  • APIs HTTP/REST
  • gRPC y Protocol Buffers
  • Microservicios
  • CLI tools con Cobra
Ir a temas →

📝 Ejemplos Rápidos

Variables y Tipos

package main

import "fmt"

func main() {
    var nombre string = "Juan"
    edad := 30
    const PI = 3.1416

    var (
        usuario = "admin"
        activo  = true
    )

    fmt.Println(nombre, edad, PI, usuario, activo)
}

Funciones con Múltiples Retornos

// Múltiples retornos
func dividir(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("división por cero")
    }
    return a / b, nil
}

// Función variádica
func sumar(numeros ...int) int {
    total := 0
    for _, n := range numeros {
        total += n
    }
    return total
}

Concurrencia con Goroutines

func trabajador(id int, jobs <-chan int, results chan<- int) {
    for job := range jobs {
        results <- job * 2
    }
}

func main() {
    jobs := make(chan int, 10)
    results := make(chan int, 10)

    for w := 1; w <= 3; w++ {
        go trabajador(w, jobs, results)
    }

    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)
}

📖 Recursos Adicionales

Herramientas

  • VS Code + Go extension
  • GoLand - IDE de JetBrains
  • golangci-lint

Comunidades

👨‍💻 Desarrollado por Isaac Esteban Haro Torres

Ingeniero en Sistemas · Full Stack · Automatización · Data

📧 Email: zackharo1@gmail.com

📱 WhatsApp: 098805517

💻 GitHub: github.com/ieharo1

🌐 Portafolio: ieharo1.github.io/portafolio-isaac.haro/