Introdução: Por que a arquitetura não é um luxo, mas uma necessidade
\n\nQuando você começa a programar em Go, parece que tudo é simples: as goroutines voam, os canais zumbem e o compilador agrada pela velocidade. Mas passa um mês ou dois, e seu projeto-pet se transforma em um monstro com package main de 3000 linhas e orações "tomara que não quebre". Familiar?
Go oferece ferramentas poderosas, mas não impõe estruturas rígidas. Isso é um ponto positivo e negativo. Sem uma arquitetura clara, você rapidamente se perderá em suas próprias goroutines. É aqui que entram os padrões arquiteturais em Go — soluções comprovadas para problemas típicos de design.
\n\nNeste artigo, vamos analisar quatro padrões principais que realmente simplificarão sua vida: Repository, Service Layer, Worker Pool e Pipeline. Você verá exemplos de código, entenderá quando aplicar cada padrão e poderá implementá-los imediatamente em seus projetos. Vamos lá!
\n\n1. Repository Pattern: separando a lógica de negócios do banco de dados
\n\nO primeiro padrão que todo desenvolvedor Go deve dominar é o Repository. Sua essência é simples: criar uma camada entre a lógica de negócios e o armazenamento de dados (BD, arquivos, APIs externas).
\n\nPor que isso é necessário?
\n- \n
- Testabilidade: é possível substituir facilmente o BD real por um mock. \n
- Flexibilidade: migrar do PostgreSQL para o MongoDB? Basta alterar o repositório. \n
- Limpeza do código: o handler não sabe de onde vêm os dados — essa é a função do repositório. \n
Exemplo de implementação
\nSuponha que temos um modelo de usuário:
\n\ntype User struct {\n ID int64\n Name string\n Email string\n}\n\nDefinimos a interface do repositório:
\n\ntype 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\nE sua implementação para PostgreSQL:
\n\ntype 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\nAgora seu handler ou serviço trabalha apenas com a interface UserRepository, e não com um BD específico. Isso é o padrão arquitetural Repository em Go em ação.
2. Service Layer: lógica de negócios sob controle
\n\nSe o Repository é responsável pelos dados, o Service Layer é responsável pelas regras de negócios. Esse padrão reúne operações dispersas em ações significativas.
\n\nProblema sem o Service Layer
\nFrequentemente vejo código onde o handler faz tudo: valida a requisição, verifica permissões, escreve no BD e registra logs. Isso viola o princípio da responsabilidade única (SRP).
\n\nComo corrigir isso?
\nIntroduzimos a camada de serviço:
\n\ntype 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. Validação\n if name == "" || email == "" {\n return nil, ErrInvalidInput\n }\n\n // 2. Lógica de negócios: verificar se o email já está em uso\n existing, _ := s.repo.GetByEmail(ctx, email)\n if existing != nil {\n return nil, ErrEmailAlreadyExists\n }\n\n // 3. Criar usuário\n user := &User{Name: name, Email: e