Enterprise Patterns en Golang: Desacoplando tu código con el Patrón Repositorio
Source: Dev.to
Introducción: El problema del acoplamiento
Cuando leemos Patterns of Enterprise Application Architecture de Martin Fowler, a veces pensamos en sistemas monolíticos antiguos de Java. Sin embargo, estos patrones son más relevantes que nunca en la era de los microservicios.
Un error común en aplicaciones Go es mezclar las consultas SQL (SELECT * FROM …) directamente dentro de los controladores HTTP o la lógica de negocio. Esto hace que el código sea imposible de testear y difícil de migrar.
Hoy implementaremos el Repository Pattern. Este patrón crea una capa de abstracción entre el dominio (tu lógica) y el mapeo de datos (tu base de datos), permitiendo que tu aplicación evolucione sin romper el núcleo del negocio.

El escenario
Imaginemos un sistema empresarial de gestión de empleados. Necesitamos crear un empleado y guardarlo. No nos importa si se guarda en Postgres, MySQL o en memoria; nuestra lógica de negocio no debería saberlo.
Implementación en Go
Entidad (Dominio)
// domain/employee.go
package domain
type Employee struct {
ID string
Name string
Role string
Salary float64
}
Interfaz del repositorio
// domain/repository.go
package domain
// EmployeeRepository define el contrato para acceder a los datos
type EmployeeRepository interface {
Save(employee *Employee) error
FindByID(id string) (*Employee, error)
}
Service Layer (Lógica de negocio)
// service/employee_service.go
package service
import (
"errors"
"enterprise-patterns/domain"
)
type EmployeeService struct {
repo domain.EmployeeRepository
}
func NewEmployeeService(repo domain.EmployeeRepository) *EmployeeService {
return &EmployeeService{repo: repo}
}
func (s *EmployeeService) RegisterEmployee(id, name, role string) error {
if name == "" {
return errors.New("el nombre no puede estar vacío")
}
// Regla empresarial: solo los gerentes pueden tener bonos iniciales (simulado aquí)
emp := &domain.Employee{
ID: id,
Name: name,
Role: role,
}
return s.repo.Save(emp)
}
Implementación concreta (Infraestructura)
// infrastructure/memory_repo.go
package infrastructure
import (
"errors"
"enterprise-patterns/domain"
)
type InMemoryEmployeeRepo struct {
store map[string]*domain.Employee
}
func NewInMemoryRepo() *InMemoryEmployeeRepo {
return &InMemoryEmployeeRepo{
store: make(map[string]*domain.Employee),
}
}
func (r *InMemoryEmployeeRepo) Save(employee *domain.Employee) error {
if _, exists := r.store[employee.ID]; exists {
return errors.New("el empleado ya existe")
}
r.store[employee.ID] = employee
return nil
}
func (r *InMemoryEmployeeRepo) FindByID(id string) (*domain.Employee, error) {
if emp, ok := r.store[id]; ok {
return emp, nil
}
return nil, errors.New("empleado no encontrado")
}
Uso en main.go
// main.go
package main
import (
"fmt"
"enterprise-patterns/infrastructure"
"enterprise-patterns/service"
)
func main() {
// 1. Crear la implementación concreta (Infraestructura)
repo := infrastructure.NewInMemoryRepo()
// 2. Inyectar el repositorio en el servicio (Dominio)
empService := service.NewEmployeeService(repo)
// 3. Ejecutar la lógica
err := empService.RegisterEmployee("001", "Carlos Dev", "Senior Engineer")
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Empleado registrado exitosamente usando Repository Pattern!")
}
}
Conclusión
Al usar el Repository Pattern, hemos logrado que nuestro EmployeeService sea 100 % agnóstico de la base de datos. Si mañana queremos cambiar de memoria a MongoDB, solo creamos un nuevo archivo en infrastructure y no tocamos ni una línea de la lógica de negocio. Eso es arquitectura empresarial robusta.