En esta página

Proyecto final: gestor de tareas CLI con API HTTP

25 min lectura TextoCap. 5 — Go en producción

Proyecto final: gestor de tareas CLI con API HTTP

¡Enhorabuena por llegar al proyecto final! Has recorrido todo el ecosistema esencial de Go: tipos, funciones, structs, interfaces, goroutines, channels, manejo de errores, módulos y HTTP. Ahora vas a integrar todo ese conocimiento construyendo un proyecto real y completo.

¿Qué construirás?

Un gestor de tareas que funciona de dos modos:

  1. Modo CLI: interactúas desde la línea de comandos (listar, crear, completar, eliminar)
  2. Modo servidor HTTP: expone una API REST en localhost:8080

Los datos se persisten en un archivo JSON, la concurrencia está protegida con sync.RWMutex, y el servidor se cierra con gracia capturando señales del sistema operativo.

Arquitectura del proyecto

tareas-go/
├── go.mod
└── main.go          ← todo en un archivo para este proyecto

El diseño sigue estos principios de Go:

  • Interfaz Repositorio: desacopla la lógica de negocio de la implementación de almacenamiento
  • Struct RepoJSON: implementación concreta con persistencia en archivo
  • Concurrencia segura: sync.RWMutex protege el mapa de tareas
  • Graceful shutdown: el servidor HTTP escucha señales del OS para cerrarse limpiamente
  • Error handling explícito: cada función retorna error; los errores centinela permiten comparación con errors.Is

Conceptos aplicados

1. Interfaces para desacoplamiento

La interfaz Repositorio define el contrato que cualquier implementación de almacenamiento debe cumplir. El CLI y el servidor trabajan exclusivamente con esta interfaz:

type Repositorio interface {
    Crear(título, descripción string, prioridad int) (*Tarea, error)
    Listar() ([]*Tarea, error)
    ObtenerPorID(id int) (*Tarea, error)
    Actualizar(tarea *Tarea) error
    Eliminar(id int) error
}

Para agregar almacenamiento en PostgreSQL, solo necesitarías crear una RepoPostgres struct que implemente los mismos 5 métodos.

2. Concurrencia con sync.RWMutex

El servidor HTTP recibe múltiples requests concurrentes. El mapa de tareas debe protegerse:

// Múltiples lecturas concurrentes están permitidas
func (r *RepoJSON) Listar() ([]*Tarea, error) {
    r.mu.RLock()        // múltiples goroutines pueden leer simultáneamente
    defer r.mu.RUnlock()
    // ...
}

// Solo una escritura a la vez
func (r *RepoJSON) Crear(...) (*Tarea, error) {
    r.mu.Lock()         // bloquea TODO: lectores y escritores
    defer r.mu.Unlock()
    // ...
}

3. Pattern matching de ServeMux (Go 1.22+)

mux.HandleFunc("GET /api/tareas", listar)
mux.HandleFunc("POST /api/tareas", crear)
mux.HandleFunc("GET /api/tareas/{id}", obtenerPorID)
mux.HandleFunc("DELETE /api/tareas/{id}", eliminar)

El método HTTP en el patrón garantiza que GET /api/tareas y POST /api/tareas van a handlers diferentes automáticamente.

4. Graceful shutdown con channels y select

señales := make(chan os.Signal, 1)
signal.Notify(señales, os.Interrupt, syscall.SIGTERM)
errCh := make(chan error, 1)

go func() {
    errCh <- srv.ListenAndServe()
}()

select {
case err := <-errCh:
    // El servidor terminó por error
case <-señales:
    // Ctrl+C o SIGTERM: cerrar con gracia
    srv.Shutdown(ctx)
}

Cómo ejecutar el proyecto

# Inicializar el módulo
go mod init github.com/tuusuario/tareas-go

# Listar tareas
go run main.go listar

# Crear una tarea
go run main.go crear --titulo "Estudiar Go" --desc "Completar el curso" --prioridad 3

# Completar una tarea
go run main.go completar 1

# Iniciar el servidor HTTP
go run main.go servidor

# En otra terminal, probar la API
curl http://localhost:8080/api/tareas
curl -X POST http://localhost:8080/api/tareas \
  -H "Content-Type: application/json" \
  -d '{"titulo":"Nueva tarea","prioridad":2}'
curl http://localhost:8080/api/tareas/1
curl -X DELETE http://localhost:8080/api/tareas/1

Extensiones posibles

Una vez que el proyecto base funcione, puedes extenderlo con:

  1. Filtros en el listado: GET /api/tareas?estado=pendiente&prioridad=3
  2. Autenticación básica: middleware que verifica un API key en el header
  3. Paginación: GET /api/tareas?pagina=2&por_pagina=10
  4. Fechas de vencimiento: campo VenceEn *time.Time en Tarea
  5. Notificaciones: goroutine que verifica tareas vencidas y las notifica
  6. SQLite: implementar RepoSQLite con database/sql y el driver modernc.org/sqlite
  7. Tests: RepoEnMemoria para pruebas unitarias sin archivos

Lo que aprendiste en este curso

Con este proyecto completas tu dominio de Go esencial:

  • Sistema de tipos estático, fuerte y expresivo
  • Funciones con múltiples retornos y el patrón (valor, error)
  • Structs, métodos e interfaces implícitas
  • Slices y maps con sus idioms específicos
  • Punteros seguros sin aritmética de punteros
  • Manejo de errores explícito con errors.Is y errors.As
  • Goroutines — millones posibles con 2-4 KB cada una
  • Channels para comunicación segura entre goroutines
  • Patrones de concurrencia: worker pool, fan-in/out, pipeline
  • context.Context para cancelación propagada
  • Sistema de módulos y paquetes
  • API REST con net/http y ServeMux de Go 1.22+

Go es el lenguaje de la infraestructura moderna. Docker, Kubernetes, Terraform, Prometheus — todos nacieron en Go. Con este curso tienes las bases para construir herramientas de la misma calidad.

El siguiente paso natural es explorar el ecosystem de Go: gorm o sqlx para bases de datos, gin o chi para routing avanzado, y testify para pruebas más expresivas.

La interfaz Repositorio permite cambiar la implementación sin tocar el resto
El código CLI y el servidor HTTP trabajan con la interfaz Repositorio, no con RepoJSON directamente. Esto significa que puedes crear una implementación PostgreSQL, SQLite o en memoria, y el resto del código funciona sin cambios. Este es el poder del polimorfismo de interfaces en Go.
sync.RWMutex protege el acceso concurrente al mapa en el servidor HTTP
El servidor HTTP puede recibir múltiples requests simultáneos, cada uno en su propia goroutine. Sin el RWMutex, múltiples goroutines leerían y escribirían el mapa de tareas concurrentemente — una carrera de datos. RLock() permite lecturas concurrentes, Lock() garantiza escritura exclusiva.