A forma como está agora não me parece adequada; o whatsapp-js é frágil – depende do Puppeteer/Chromium headless e frequentemente apresenta problemas com as atualizações do WhatsApp Web (este problema não foi resolvido em um comentário específico em outra questão, o que deve estar causando problemas, já que o projeto ainda não foi atualizado para versões mais recentes da biblioteca).
Migração whatsapp/ → Go
Diagnóstico da Arquitetura Atual
O módulo whatsapp/ é um serviço Node.js de arquivo único (src/index.js, ~400 linhas) que funciona como adapter entre o WhatsApp e o timeless-api. A arquitetura atual apresenta problemas fundamentais de escalabilidade e manutenibilidade.
Problemas Que Eu identifiquei
Aspecto
Problema
Runtime
Node.js + Chromium headless (~500MB imagem Docker) — pesado, frágil, startup lento
Arquitetura
Single-file (src/index.js) — sem separação de camadas, difícil testar e manter
Dual path
Texto → SQS (async), Mídia → REST (sync) — inconsistente, duplica lógica de dispatch
Sessão
wwebjs-auth/ em filesystem — impede escalabilidade horizontal (sticky session obrigatória)
Redis
Declarado no docker-compose.yaml mas zero uso em código
Dependência crítica
whatsapp-web.js automatiza WhatsApp Web via Puppeteer — não é API oficial, pode quebrar a qualquer atualização do WhatsApp
Tipagem
JavaScript puro — contratos SQS/S3/REST sem validação em compile-time
Fluxo Atual
WhatsApp User
│
│ (WhatsApp message)
▼
┌──────────────────────────┐
│ whatsapp/ (Node.js) │
│ whatsapp-web.js + │
│ Chromium headless │
└──────┬───────┬───────────┘
│ │
│ ├── [Audio] → OpenAI Whisper → POST /api/messages (REST)
│ ├── [Image] → POST /api/messages/image (REST)
│ └── [Text] → SQS: incoming-messages.fifo
│
▼
┌──────────────────────────┐
│ timeless-api (Quarkus) │
│ AI processing + DB │
└──────┬───────────────────┘
│
▼
SQS: messages-processed.fifo
│
▼
┌──────────────────────────┐
│ whatsapp/ (sqs-consumer)│
│ → WhatsApp reply │
└──────────────────────────┘
Dois caminhos de integração existem simultaneamente:
Síncrono REST — mídia (imagem/áudio) chama timeless-api diretamente e espera resposta
Assíncrono SQS — texto vai para fila, timeless-api consome, processa com IA, responde via outbox pattern ( isso está num pull request que está como rascunho ainda Use microprofile reactive-messaging for SQS and add pattern resilience for messaging #220
Por que Go
Benefícios Concretos
Métrica
Node.js (atual)
Go (proposto)
Tamanho da imagem Docker
~500MB (Node + Chromium)
~15-20MB (alpine + binário estático)
Consumo de RAM
200-500MB (Chromium)
20-50MB
Startup
10-30s (Chromium boot)
<100ms
Instâncias por nó
1-2
10-20+
Concorrência
Event loop (single-threaded)
Goroutines (milhares leves)
Tipagem
Dinâmica (JS)
Estática (compile-time)
Deployment
npm install + Chromium install
Binário único
Dependência Chromium
Sim — frágil, pesado
Não — usa API oficial do Meta
Por que Go especificamente
Goroutines + channels — concorrência nativa para polling SQS, chamadas HTTP, upload S3 em paralelo, sem o overhead de threads do SO
Binário único compilado — sem runtime, sem node_modules, sem Chromium — deployment simplificado
Stdlib robusta — net/http, encoding/json, crypto/*, os/signal cobrem a maior parte das necessidades
Cross-compile — GOOS=linux GOARCH=amd64 go build gera binário para qualquer ambiente
Tipagem estática — contratos de mensagem SQS, responses da API, structs S3 validados antes do runtime
Ecossistema AWS maduro — SDK v2 oficial com tipagem forte, retry, middleware
Stack Go Recomendada
Responsabilidade
Pacote
Motivo
WhatsApp Integration
net/http + WhatsApp Cloud API
Elimina Puppeteer/Chromium — API oficial do Meta, estável e suportada
SQS Producer
github.com/aws/aws-sdk-go-v2/service/sqs
SDK oficial AWS v2, tipado, com retry e middleware
SQS Consumer
github.com/aws/aws-sdk-go-v2/service/sqs + goroutine custom
Long-polling com ReceiveMessage — simples com goroutines, sem lib extra
S3 Upload
github.com/aws/aws-sdk-go-v2/service/s3
SDK oficial, consistente com SQS
Whisper (transcrição)
github.com/sashabaranov/go-openai
Client OpenAI maduro, suporta Whisper com CreateTranscription
HTTP Client (timeless-api)
net/http + contexto com timeout
Stdlib suficiente, sem framework
Config
github.com/caarlos0/env
Parsing de env vars para struct tipada com validação
Env local
github.com/joho/godotenv
Compatibilidade com .env existente
Logging
log/slog (Go 1.21+)
Structured logging nativo, zero dependência
Graceful shutdown
os/signal + context.WithCancel
Padrão idiomático Go
Testes
testing (stdlib) + github.com/stretchr/testify
Assertions legíveis, mocking via interfaces
Mudança Arquitetural Fundamental
De Puppeteer → WhatsApp Cloud API (Webhooks)
Hoje: Node.js abre browser headless, escaneia QR code, mantém sessão Chromium em filesystem. Impede escalabilidade horizontal.
Go: Meta envia POST (webhook) para cada mensagem recebida. O bot responde via POST para a Cloud API. Sem browser, sem sessão em filesystem, sem QR code.
┌─────────────────────────────────────────────────┐
│ Meta Cloud │
│ WhatsApp Cloud API (webhook → POST no bot) │
└────────────┬────────────────────┬───────────────┘
│ │
[Webhook: mensagem] [Send: resposta]
│ │
▼ │
┌────────────────────┐ │
│ whatsapp-go │ │
│ (Go binary) │───────────┘
│ │
│ ├─ /webhook │ ← Meta envia mensagens aqui
│ ├─ SQS producer │ → incoming-messages.fifo
│ ├─ SQS consumer │ ← messages-processed.fifo
│ ├─ Whisper client │ → OpenAI API
│ ├─ S3 uploader │ → AWS S3
│ └─ API client │ → timeless-api (REST)
└────────────────────┘
Vantagens da Cloud API sobre Puppeteer
Stateless — N réplicas horizontalmente, sem sticky session
Sem Chromium — elimina a maior fonte de instabilidade e custo
Oficial — suportada pelo Meta, não quebra com atualizações do WhatsApp Web
Sem QR code — autenticação via token, não via sessão de browser ( isso aqui pode ser um exagero da minha parte, pode desconsiderar isso, podemos planejar outra forma
Webhook — push ao invés de polling no browser
Arquitetura Proposta em Go
whatsapp/
├── cmd/
│ └── bot/
│ └── main.go # Entry point: config, wiring, graceful shutdown
├── internal/
│ ├── config/
│ │ └── config.go # Struct tipada de env vars
│ ├── domain/
│ │ └── message.go # Tipos: IncomingMessage, ProcessedMessage, etc.
│ ├── whatsapp/
│ │ ├── client.go # WhatsApp Cloud API client (enviar mensagens)
│ │ └── webhook.go # Handler para webhooks do Meta (recebimento)
│ ├── sqs/
│ │ ├── producer.go # Envio para incoming-messages.fifo
│ │ └── consumer.go # Long-poll de messages-processed.fifo
│ ├── s3/
│ │ └── uploader.go # Upload de mídia
│ ├── transcription/
│ │ └── whisper.go # OpenAI Whisper client
│ ├── api/
│ │ └── timeless.go # HTTP client para timeless-api
│ └── handler/
│ ├── text.go # Handler: texto → SQS
│ ├── audio.go # Handler: áudio → Whisper → timeless-api REST
│ └── image.go # Handler: imagem → timeless-api REST
├── pkg/
│ └── httpserver/
│ └── server.go # HTTP server para webhooks + healthz
├── Dockerfile # Multi-stage: build → scratch/alpine, ~15MB
├── docker-compose.yaml
├── go.mod
├── go.sum
└── .env.example
Detalhamento dos Componentes
cmd/bot/main.go — Entry Point
func main () {
cfg := config .Load () // Struct tipada de env vars
ctx , stop := signal .NotifyContext (context .Background (), os .Interrupt , syscall .SIGTERM )
defer stop ()
sqsClient := sqs .NewClient (cfg .AWS ) // SQS producer + consumer
s3Client := s3 .NewUploader (cfg .AWS ) // S3 uploader
whisperClient := transcription .New (cfg .OpenAI )// Whisper
apiClient := api .NewClient (cfg .TimelessAPI ) // timeless-api HTTP
waClient := whatsapp .NewClient (cfg .WhatsApp ) // Cloud API send
handler := handler .New (handler.Deps {
SQS : sqsClient ,
S3 : s3Client ,
Whisper : whisperClient ,
API : apiClient ,
WhatsApp : waClient ,
})
// Webhook receiver (Meta push)
mux := http .NewServeMux ()
mux .HandleFunc ("GET /webhook" , webhook .Verify (cfg .WhatsApp .VerifyToken ))
mux .HandleFunc ("POST /webhook" , webhook .Handle (handler ))
mux .HandleFunc ("GET /healthz" , healthz )
srv := & http.Server {Addr : ":" + cfg .Port , Handler : mux }
// SQS consumer em goroutine separada
go sqsClient .Consume (ctx , handler .OnProcessedMessage )
// Graceful shutdown
go func () {
<- ctx .Done ()
shutdownCtx , cancel := context .WithTimeout (context .Background (), 10 * time .Second )
defer cancel ()
srv .Shutdown (shutdownCtx )
}()
srv .ListenAndServe ()
}
internal/domain/message.go — Contratos Tipados
type IncomingMessage struct {
Sender string `json:"sender"`
Kind string `json:"kind"` // TEXT, IMAGE, AUDIO
MessageID string `json:"messageId"`
MessageBody string `json:"messageBody"`
MediaBase64 string `json:"mediaBase64,omitempty"`
MimeType string `json:"mimeType,omitempty"`
}
type ProcessedMessage struct {
Kind string `json:"kind"` // GET_BALANCE, ADD_TRANSACTION
Sender string `json:"sender"`
Metadata map [string ]interface {} `json:"metadata"`
}
internal/sqs/consumer.go — Long-Polling com Goroutines
func (c * Consumer ) Consume (ctx context.Context , handler func (ProcessedMessage )) {
for {
select {
case <- ctx .Done ():
return
default :
}
output , err := c .client .ReceiveMessage (ctx , & sqs.ReceiveMessageInput {
QueueUrl : c .queueURL ,
MaxNumberOfMessages : 10 ,
WaitTimeSeconds : 20 , // Long-polling
})
if err != nil {
slog .Error ("sqs receive error" , "err" , err )
continue
}
var wg sync.WaitGroup
sem := make (chan struct {}, 10 ) // Concurrency limit
for _ , msg := range output .Messages {
sem <- struct {}{}
wg .Add (1 )
go func (m types.Message ) {
defer wg .Done ()
defer func () { <- sem }()
var processed ProcessedMessage
json .Unmarshal ([]byte (* m .Body ), & processed )
handler (processed )
c .client .DeleteMessage (ctx , & sqs.DeleteMessageInput {
QueueUrl : c .queueURL ,
ReceiptHandle : m .ReceiptHandle ,
})
}(msg )
}
wg .Wait ()
}
}
internal/handler/text.go — Unificação do Fluxo
Com a Cloud API, todas as mensagens chegam via webhook . O handler decide o roteamento:
func (h * Handler ) OnWebhookMessage (msg IncomingMessage ) error {
switch msg .Kind {
case "TEXT" :
return h .routeText (msg ) // → SQS (async, mantém padrão atual)
case "AUDIO" :
return h .routeAudio (msg ) // → Whisper → timeless-api REST
case "IMAGE" :
return h .routeImage (msg ) // → timeless-api REST
}
}
func (h * Handler ) OnProcessedMessage (msg ProcessedMessage ) error {
return h .WhatsApp .SendText (msg .Sender , formatReply (msg ))
}
Dockerfile Proposto
# Build
FROM golang:1.26-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o /bot ./cmd/bot
# Runtime
FROM alpine:3.20
RUN apk --no-cache add ca-certificates
COPY --from=builder /bot /bot
EXPOSE 8080
ENTRYPOINT ["/bot" ]
Resultado: Imagem ~15-20MB (vs ~500MB atual).
Estratégia de Migração (Gradual)
Fase 0 — Preparação (1-2 semanas)
Fase 1 — Shadow Mode (2-3 semanas)
Fase 2 — Cutover (1 semana)
Fase 3 — Cleanup (1 semana)
Escalabilidade — Como Go Resolve os Gargalos Atuais
Gargalo Atual
Solução em Go
1 instância apenas (sessão Chromium em filesystem)
Cloud API é stateless → N réplicas horizontalmente, sem sticky session
Chromium consome ~300-500MB RAM
Binário Go ~20-50MB → mais instâncias por nó
sqs-consumer processa 1 msg por vez
Goroutine pool com semaphore — N mensagens em paralelo
Startup lento (~10-30s para Chromium)
Startup em <100ms — auto-scaling reativo
Sem health check estruturado
/healthz endpoint — pronto para k8s readiness/liveness probe
Mídia síncrono bloqueia event loop
go func() — I/O totalmente paralelo, sem bloqueio
Sem métricas
expvar (stdlib) ou prometheus/client_golang — métricas de fila, latência, throughput
Escalabilidade Horizontal
Com a Cloud API, o serviço é stateless — qualquer instância pode processar qualquer mensagem. Isso permite:
Auto-scaling baseado em comprimento da fila SQS (CloudWatch alarm → scale out/in)
Zero downtime deploys — novas instâncias registram e começam a receber webhooks imediatamente
Multi-AZ — instâncias em zonas de disponibilidade diferentes sem restrição de sessão
Rolling updates — substituição gradual sem interrupção
Concorrência
// Processamento paralelo de mensagens com backpressure controlado
sem := make (chan struct {}, runtime .NumCPU ()* 2 ) // Limite baseado em CPU
for msg := range messages {
sem <- struct {}{}
go func (m Message ) {
defer func () { <- sem }()
process (m )
}(msg )
}
Pontos de Atenção
WhatsApp Cloud API
Rate limits — limites por número e por template. Importante para mensagens em massa
Webhook exige HTTPS + verificação do Meta — precisa de infra de TLS (ALB, CloudFront, ou ngrok para dev)
Templates de mensagem — mensagens proativas (fora da janela de 24h) exigem templates pré-aprovados pelo Meta
Custo — gratuita para conversas iniciadas pelo usuário (janela 24h), mas tem custo por conversa iniciada pelo business
Migração de sessões — usuários não precisam re-autenticar (Cloud API é independente de sessão do browser)
Compatibilidade com timeless-api
Os endpoints REST (POST /api/messages, POST /api/messages/image) permanecem inalterados — o bot Go é um drop-in replacement
Os contratos SQS (incoming-messages.fifo, messages-processed.fifo) permanecem os mesmos — basta serializar os mesmos JSONs
A IAM já está configurada no Terraform — apenas ajustar se necessário para Cloud API
Testabilidade
Cada componente (sqs, s3, whatsapp, transcription, api) deve ser definido como interface — permite mocking em testes
Testes de integração com LocalStack (já usado no projeto) para SQS e S3
Testes end-to-end com webhook mockado
Resumo: O que Deve Ser Usado
Item
Escolha
Justificativa
Linguagem
Go 1.23+
Concorrência nativa, binário único, baixo consumo
WhatsApp
Cloud API (webhooks)
Oficial, stateless, sem Chromium
AWS SDK
aws-sdk-go-v2
Tipado, retry, middleware oficial
OpenAI
go-openai (sashabaranov)
Maduro, suporta Whisper
HTTP
net/http (stdlib)
Suficiente, sem framework
Logging
log/slog (stdlib)
Structured logging nativo
Config
caarlos0/env + godotenv
Struct tipada, compatibilidade .env
Testes
testing + testify
Idiomático + assertions legíveis
Container
alpine:3.20 + binário estático
~15-20MB imagem final
Métricas
expvar ou prometheus/client_golang
Observabilidade desde o início
CI/CD
Reutilizar pipelines existentes
Ajustar para go build e imagem Docker nova
A forma como está agora não me parece adequada; o whatsapp-js é frágil – depende do Puppeteer/Chromium headless e frequentemente apresenta problemas com as atualizações do WhatsApp Web (este problema não foi resolvido em um comentário específico em outra questão, o que deve estar causando problemas, já que o projeto ainda não foi atualizado para versões mais recentes da biblioteca).
Migração whatsapp/ → Go
Diagnóstico da Arquitetura Atual
O módulo
whatsapp/é um serviço Node.js de arquivo único (src/index.js, ~400 linhas) que funciona como adapter entre o WhatsApp e otimeless-api. A arquitetura atual apresenta problemas fundamentais de escalabilidade e manutenibilidade.Problemas Que Eu identifiquei
src/index.js) — sem separação de camadas, difícil testar e manterwwebjs-auth/em filesystem — impede escalabilidade horizontal (sticky session obrigatória)docker-compose.yamlmas zero uso em códigowhatsapp-web.jsautomatiza WhatsApp Web via Puppeteer — não é API oficial, pode quebrar a qualquer atualização do WhatsAppFluxo Atual
Dois caminhos de integração existem simultaneamente:
timeless-apidiretamente e espera respostatimeless-apiconsome, processa com IA, responde via outbox pattern ( isso está num pull request que está como rascunho ainda Use microprofile reactive-messaging for SQS and add pattern resilience for messaging #220Por que Go
Benefícios Concretos
Por que Go especificamente
node_modules, sem Chromium — deployment simplificadonet/http,encoding/json,crypto/*,os/signalcobrem a maior parte das necessidadesGOOS=linux GOARCH=amd64 go buildgera binário para qualquer ambienteStack Go Recomendada
net/http+ WhatsApp Cloud APIgithub.com/aws/aws-sdk-go-v2/service/sqsgithub.com/aws/aws-sdk-go-v2/service/sqs+ goroutine customReceiveMessage— simples com goroutines, sem lib extragithub.com/aws/aws-sdk-go-v2/service/s3github.com/sashabaranov/go-openaiCreateTranscriptionnet/http+ contexto com timeoutgithub.com/caarlos0/envgithub.com/joho/godotenv.envexistentelog/slog(Go 1.21+)os/signal+context.WithCanceltesting(stdlib) +github.com/stretchr/testifyMudança Arquitetural Fundamental
De Puppeteer → WhatsApp Cloud API (Webhooks)
Hoje: Node.js abre browser headless, escaneia QR code, mantém sessão Chromium em filesystem. Impede escalabilidade horizontal.
Go: Meta envia POST (webhook) para cada mensagem recebida. O bot responde via POST para a Cloud API. Sem browser, sem sessão em filesystem, sem QR code.
Vantagens da Cloud API sobre Puppeteer
Arquitetura Proposta em Go
Detalhamento dos Componentes
cmd/bot/main.go— Entry Pointinternal/domain/message.go— Contratos Tipadosinternal/sqs/consumer.go— Long-Polling com Goroutinesinternal/handler/text.go— Unificação do FluxoCom a Cloud API, todas as mensagens chegam via webhook. O handler decide o roteamento:
Dockerfile Proposto
Resultado: Imagem ~15-20MB (vs ~500MB atual).
Estratégia de Migração (Gradual)
Fase 0 — Preparação (1-2 semanas)
WHATSAPP_TOKEN,PHONE_NUMBER_ID,VERIFY_TOKENwhatsapp-go/com a estrutura propostaconfig.go,domain/message.go,main.goskeletongo.modcom dependênciasFase 1 — Shadow Mode (2-3 semanas)
/webhookGET verify + POST handler)incoming-messages.fifo)messages-processed.fifo)timeless-api(POST /api/messages,POST /api/messages/image)Fase 2 — Cutover (1 semana)
Fase 3 — Cleanup (1 semana)
whatsapp/(Node.js) do repositóriodocker-compose.yamlse ainda não usadoEscalabilidade — Como Go Resolve os Gargalos Atuais
/healthzendpoint — pronto para k8s readiness/liveness probego func()— I/O totalmente paralelo, sem bloqueioexpvar(stdlib) ouprometheus/client_golang— métricas de fila, latência, throughputEscalabilidade Horizontal
Com a Cloud API, o serviço é stateless — qualquer instância pode processar qualquer mensagem. Isso permite:
Concorrência
Pontos de Atenção
WhatsApp Cloud API
Compatibilidade com timeless-api
POST /api/messages,POST /api/messages/image) permanecem inalterados — o bot Go é um drop-in replacementincoming-messages.fifo,messages-processed.fifo) permanecem os mesmos — basta serializar os mesmos JSONsTestabilidade
sqs,s3,whatsapp,transcription,api) deve ser definido como interface — permite mocking em testesResumo: O que Deve Ser Usado
go builde imagem Docker nova