Go nos grandes negócios: por que já não é apenas uma "linguagem para microsserviços"
\n\nQuando você ouve "Enterprise Go", o que vem à mente? Regulamentos corporativos chatos? Code reviews intermináveis? Ou, talvez, a sensação de que Go é apenas para startups e DevOps?
\n\nNa verdade, Go no enterprise é um nível completamente diferente de disciplina e cultura de engenharia. Em grandes empresas (Uber, Dropbox, Salesforce, Twitch), não se escrevem apenas microsserviços em Go, mas plataformas inteiras que processam milhões de requisições por segundo. E aqui, "apenas funciona" não é suficiente.
\n\nNeste artigo, vamos analisar as principais características do desenvolvimento em Go no enterprise: desde o design de pacotes até a logging em produção. Haverá muito código e o mínimo de enrolação.
\n\n1. Arquitetura: não repita o erro "monólito em microsserviços"
\n\nO problema mais comum que vejo em projetos Go de nível enterprise é a tentativa de transferir a arquitetura estilo Java para o Go. Resultado: centenas de interfaces desnecessárias e camadas de abstração que apenas atrasam o desenvolvimento.
\n\nRegra "Package by feature, not by layer"
\n\nEm vez de criar pastas /models, /handlers, /services — agrupe por domínios de negócio:
myapp/\n├── cmd/\n│ └── server/\n│ └── main.go\n├── internal/\n│ ├── user/ # Tudo relacionado a usuários\n│ │ ├── handler.go\n│ │ ├── service.go\n│ │ ├── repository.go\n│ │ └── user.go # Modelo de dados\n│ ├── payment/ # Tudo relacionado a pagamentos\n│ │ ├── handler.go\n│ │ ├── service.go\n│ │ ├── gateway.go\n│ │ └── payment.go\n│ └── pkg/ # Utilitários comuns\n└── go.mod\n\nEsta abordagem é uma das melhores práticas Go para enterprise. Ela reduz o acoplamento e torna o código previsível. Quando um novo desenvolvedor abre o projeto, ele vê imediatamente a lógica de negócio, e não camadas de abstração.
\n\nInterfaces: aceite interfaces, retorne structs
\n\nEste é um conselho clássico da comunidade Go, mas no enterprise é crítico. As interfaces devem ser pequenas (1-2 métodos) e definidas onde são usadas, não onde são implementadas.
\n\n// RUIM: interface definida junto com a implementação\npackage user\n\ntype UserRepository interface {\n FindByID(id string) (*User, error)\n Save(user *User) error\n}\n\ntype postgresRepo struct { ... }\n\n// BOM: interface onde é necessária\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\nPor que isso é importante? Porque no enterprise você frequentemente troca de backends (PostgreSQL -> CockroachDB, Redis -> KeyDB). Interfaces definidas no lado do consumidor permitem fazer isso sem dor.
\n\n2. Tratamento de erros: pare de ignorar _
\n\nGo não tem exceções, e isso é ótimo. Mas em projetos enterprise, erros não são apenas if err != nil. É toda uma cultura.
Wrap ou não wrap?
\n\nUse fmt.Errorf com %w para criar cadeias de erros. Isso permite preservar a pilha de chamadas e simplifica a depuração.
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("obtendo 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\nNo nível superior (em handler/middleware), use errors.Is() e errors.As() para verificar tipos de erros:
if errors.Is(err, ErrNotFo