Go en los grandes negocios: por qué ya no es solo un "lenguaje para microservicios"
\n\nCuando escuchas "Enterprise Go", ¿qué te viene a la mente? ¿Aburridas normativas corporativas? ¿Interminables revisiones de código? ¿O quizás la sensación de que Go es solo para startups y DevOps?
\n\nEn realidad, Go en la empresa es un nivel completamente diferente de disciplina y cultura de ingeniería. En grandes compañías (Uber, Dropbox, Salesforce, Twitch) no solo escriben microservicios en Go, sino plataformas enteras que procesan millones de solicitudes por segundo. Y aquí, "simplemente funciona" no es suficiente.
\n\nEn este artículo, analizaremos las características clave del desarrollo en Go en la empresa: desde el diseño de paquetes hasta el registro en producción. Habrá mucho código y mínimo contenido superfluo.
\n\n1. Arquitectura: no repitas el error del "monolito en microservicios"
\n\nEl problema más común que veo en proyectos Go de nivel empresarial es el intento de trasladar la arquitectura estilo Java a Go. El resultado: cientos de interfaces innecesarias y capas de abstracción que solo ralentizan el desarrollo.
\n\nRegla "Paquete por funcionalidad, no por capa"
\n\nEn lugar de crear carpetas /models, /handlers, /services — agrupa por dominios de negocio:
miapp/\n├── cmd/\n│ └── server/\n│ └── main.go\n├── internal/\n│ ├── user/ # Todo lo relacionado con usuarios\n│ │ ├── handler.go\n│ │ ├── service.go\n│ │ ├── repository.go\n│ │ └── user.go # Modelo de datos\n│ ├── payment/ # Todo lo relacionado con pagos\n│ │ ├── handler.go\n│ │ ├── service.go\n│ │ ├── gateway.go\n│ │ └── payment.go\n│ └── pkg/ # Utilidades comunes\n└── go.mod\n\nEste enfoque es una de las mejores prácticas de Go para la empresa. Reduce el acoplamiento y hace que el código sea predecible. Cuando un nuevo desarrollador abre el proyecto, ve inmediatamente la lógica de negocio, no capas de abstracción.
\n\nInterfaces: acepta interfaces, devuelve estructuras
\n\nEste es un consejo clásico de la comunidad Go, pero en la empresa es crítico. Las interfaces deben ser pequeñas (1-2 métodos) y definirse donde se usan, no donde se implementan.
\n\n// MAL: la interfaz se define junto a la implementación\npackage user\n\ntype UserRepository interface {\n FindByID(id string) (*User, error)\n Save(user *User) error\n}\n\ntype postgresRepo struct { ... }\n\n// BIEN: la interfaz está donde se necesita\npackage user\n\ntype Storage interface {\n Get(id string) (*User, error)\n Put(user *User) error\n}\n\ntype service struct {\n db Storage\n}\n\nfunc NewService(db Storage) *service {\n return &service{db: db}\n}\n\n¿Por qué es importante? Porque en la empresa a menudo cambias de backend (PostgreSQL -> CockroachDB, Redis -> KeyDB). Las interfaces definidas del lado del consumidor permiten hacerlo sin dolor.
\n\n2. Manejo de errores: deja de ignorar _
\n\nGo no tiene excepciones, y eso es genial. Pero en proyectos empresariales, los errores no son solo if err != nil. Es toda una cultura.
¿Envolver o no envolver?
\n\nUsa fmt.Errorf con %w para crear cadenas de errores. Esto permite conservar la pila de llamadas y simplifica la depuración.
func (s *service) ProcessOrder(ctx context.Context, orderID string) error {\n order, err := s.orderRepo.Get(ctx, orderID)\n if err != nil {\n return fmt.Errorf("obteniendo pedido %s: %w", orderID, err)\n }\n\n if err := s.paymentGateway.Charge(ctx, order.Amount); err != nil {\n return fmt.Errorf("cobrando pedido %s: %w", orderID, err)\n }\n\n return nil\n}\n\nEn el nivel superior (en handler/middleware) usa errors.Is() y errors.As() para verificar tipos de errores:
if errors.Is(err, ErrNotFo