Patrones arquitectónicos en Go: cómo no ahogarse en gorutinas

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: Por qué la arquitectura no es un lujo, sino una necesidad

\n\n

Cuando empiezas a programar en Go, parece que todo es sencillo: las gorutinas vuelan, los canales zumban y el compilador te alegra con su velocidad. Pero pasa un mes o dos, y tu proyecto personal se convierte en un monstruo con package main de 3000 líneas y oraciones de «ojalá no se caiga». ¿Te suena?

\n\n

Go proporciona herramientas potentes, pero no impone marcos rígidos. Esto es tanto una ventaja como una desventaja. Sin una arquitectura clara, te perderás rápidamente en tus propias gorutinas. Aquí es donde entran en juego los patrones arquitectónicos en Go: soluciones probadas para problemas de diseño típicos.

\n\n

En este artículo, analizaremos cuatro patrones clave que realmente simplificarán tu vida: Repository, Service Layer, Worker Pool y Pipeline. Verás ejemplos de código, entenderás cuándo aplicar cada patrón y podrás implementarlos de inmediato en tus proyectos. ¡Allá vamos!

\n\n

1. Repository Pattern: separamos la lógica de negocio de la base de datos

\n\n

El primer patrón que todo desarrollador Go debería dominar es Repository. Su esencia es simple: creamos una capa intermedia entre la lógica de negocio y el almacenamiento de datos (BD, archivos, APIs externas).

\n\n

¿Para qué sirve?

\n
    \n
  • Capacidad de prueba: se puede sustituir fácilmente la BD real por un mock.
  • \n
  • Flexibilidad: ¿Migrar de PostgreSQL a MongoDB? Solo cambiamos el repositorio.
  • \n
  • Código limpio: el handler no sabe de dónde vienen los datos; esa es tarea del repositorio.
  • \n
\n\n

Ejemplo de implementación

\n

Supongamos que tenemos un modelo de usuario:

\n\n
type User struct {\n    ID    int64\n    Name  string\n    Email string\n}
\n\n

Definimos la interfaz del repositorio:

\n\n
type UserRepository interface {\n    GetByID(ctx context.Context, id int64) (*User, error)\n    Create(ctx context.Context, user *User) error\n    Update(ctx context.Context, user *User) error\n    Delete(ctx context.Context, id int64) error\n}
\n\n

Y su implementación para PostgreSQL:

\n\n
type PostgresUserRepo struct {\n    db *sql.DB\n}\n\nfunc (r *PostgresUserRepo) GetByID(ctx context.Context, id int64) (*User, error) {\n    user := &User{}\n    err := r.db.QueryRowContext(ctx, "SELECT id, name, email FROM users WHERE id = $1", id).\n        Scan(&user.ID, &user.Name, &user.Email)\n    if err != nil {\n        return nil, fmt.Errorf("get user by id: %w", err)\n    }\n    return user, nil\n}
\n\n

Ahora tu handler o servicio trabaja solo con la interfaz UserRepository, no con una BD específica. Esto es el patrón arquitectónico Repository en Go en acción.

\n\n

2. Service Layer: lógica de negocio bajo control

\n\n

Si Repository se encarga de los datos, Service Layer se encarga de las reglas de negocio. Este patrón agrupa operaciones dispersas en acciones coherentes.

\n\n

El problema sin Service Layer

\n

A menudo veo código donde el handler lo hace todo: valida la solicitud, verifica permisos, escribe en la BD y registra. Esto viola el principio de responsabilidad única (SRP).

\n\n

¿Cómo solucionarlo?

\n

Introducimos una capa de servicio:

\n\n
type UserService interface {\n    Register(ctx context.Context, name, email string) (*User, error)\n    GetProfile(ctx context.Context, userID int64) (*User, error)\n}\n\ntype userService struct {\n    repo UserRepository\n    mail MailSender\n}\n\nfunc (s *userService) Register(ctx context.Context, name, email string) (*User, error) {\n    // 1. Validación\n    if name == "" || email == "" {\n        return nil, ErrInvalidInput\n    }\n\n    // 2. Lógica de negocio: verificamos si el email ya está ocupado\n    existing, _ := s.repo.GetByEmail(ctx, email)\n    if existing != nil {\n        return nil, ErrEmailAlreadyExists\n    }\n\n    // 3. Creamos el usuario\n    user := &User{Name: name, Email: e                

Blogs

Book Recommendations