Inyección de Dependencias en Go: guía práctica

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

Inyección de Dependencias en Go: de la teoría al código real



La Inyección de Dependencias (DI) es uno de los patrones de diseño clave que ayuda a crear código débilmente acoplado, testeable y mantenible. En Go, con su filosofía de simplicidad y claridad, la inyección de dependencias se puede implementar tanto manualmente como con bibliotecas. En este artículo, analizaremos cómo aplicar correctamente DI en Golang en la práctica, veremos el enfoque manual y también la popular biblioteca google/wire.



1. ¿Por qué es necesaria la Inyección de Dependencias en Go?



Imagina que estás escribiendo un servicio HTTP para trabajar con usuarios. Sin DI, tu código podría verse así:



package main

import "database/sql"

type UserHandler struct { db *sql.DB}

func NewUserHandler() *UserHandler { db, _ := sql.Open("postgres", "conn_string") return &UserHandler{db: db}}

func (h *UserHandler) GetUser(id int) (*User, error) { // trabajo con h.db}


Los problemas son evidentes:

  • Acoplamiento fuerte — el handler crea la base de datos por sí mismo.
  • Dificultad para testear — es imposible reemplazar db por un mock.
  • Violación del Principio de Responsabilidad Única — el handler es responsable tanto de crear dependencias como de la lógica de negocio.


DI resuelve estos problemas pasando las dependencias desde el exterior:



type UserHandler struct {    db Database}

func NewUserHandler(db Database) *UserHandler { return &UserHandler{db: db}}

// Ahora se puede reemplazar fácilmente por un mocktype Database interface { GetUser(id int) (*User, error)}


2. Inyección manual de dependencias: simplicidad y control



Go no requiere frameworks para DI. La forma más simple y transparente es pasar las dependencias a través de los constructores. Consideremos un ejemplo con tres niveles de aplicación: handler → service → repository.



// repository/user.gotype UserRepository interface {    FindByID(id int) (*User, error)}

type postgresUserRepo struct { db *sql.DB}

func NewPostgresUserRepo(db *sql.DB) *postgresUserRepo { return &postgresUserRepo{db: db}}

func (r *postgresUserRepo) FindByID(id int) (*User, error) { // implementación}


// service/user.gotype UserService struct {    repo UserRepository}

func NewUserService(repo UserRepository) *UserService { return &UserService{repo: repo}}

func (s *UserService) GetUser(id int) (*User, error) { return s.repo.FindByID(id)}


// handler/user.gotype UserHandler struct {    svc *UserService}

func NewUserHandler(svc *UserService) *UserHandler { return &UserHandler{svc: svc}}

func (h *UserHandler) HandleGetUser(w http.ResponseWriter, r *http.Request) { // trabajo con h.svc}


Ahora el ensamblaje de la aplicación ocurre en main.go:



func main() {    db, _ := sql.Open("postgres", os.Getenv("DB_URL"))        repo := NewPostgresUserRepo(db)    svc := NewUserService(repo)    handler := NewUserHandler(svc)        http.HandleFunc("/user", handler.HandleGetUser)    log.Fatal(http.ListenAndServe(":8080", nil))}


Ventajas del DI manual:

  • Transparencia total — ves todas las dependencias explícitamente.
  • Sin magia ni dependencias innecesarias en el proyecto.
  • Ideal para proyectos pequeños y medianos.


Desventajas: cuando la aplicación crece (50+ componentes), el ensamblaje manual se vuelve voluminoso y propenso a errores.



3. Automatización con google/wire



La biblioteca github.com/google/wire genera código para ensamblar dependencias. No es un contenedor en tiempo de ejecución, sino un generador de código, lo que se alinea completamente con la filosofía de Go.



Instalación:

go get github.com/google/wir                

Blogs

Book Recommendations