Arquitetura Multi-Plataforma de Mensageria¶
📋 Visão Geral¶
Esta refatoração transforma o bot monolítico em uma arquitetura extensível e modular que permite adicionar novas plataformas de mensageria (WhatsApp, Telegram, Discord, etc.) sem modificar o código core.
🏗️ Arquitetura¶
Componentes Principais¶
messaging/
├── __init__.py # Exports públicos
├── base.py # Interface abstrata MessagingAdapter
├── factory.py # Factory para criar adaptadores
├── processor.py # Processador central de conversas
└── adapters/
├── __init__.py
├── slack_adapter.py # Implementação Slack
├── whatsapp_adapter.py # Implementação WhatsApp (exemplo)
└── telegram_adapter.py # Implementação Telegram (exemplo)
unified_bot.py # Bot unificado multi-plataforma
slack_bot.py # [LEGACY] Bot antigo (manter por compatibilidade)
Padrões Utilizados¶
- Strategy Pattern: Cada plataforma é uma estratégia diferente
- Factory Pattern: Criação centralizada de adaptadores
- Adapter Pattern: Interface uniforme para plataformas heterogêneas
- Single Responsibility: Separação clara de responsabilidades
🔌 Componentes¶
1. MessagingAdapter (Interface Abstrata)¶
Define o contrato que todos os adaptadores devem implementar:
class MessagingAdapter(ABC):
async def setup() -> None
async def parse_webhook_request(request) -> Optional[IncomingMessage]
async def send_message(message: OutgoingMessage) -> bool
async def send_typing_indicator(channel_id, thread_id) -> None
async def update_message(message_id, new_text, channel_id) -> bool
async def delete_message(message_id, channel_id) -> bool
def format_text(text: str) -> str
@property
def platform_name() -> str
@property
def webhook_path() -> str
2. Modelos de Dados¶
IncomingMessage¶
@dataclass
class IncomingMessage:
platform: str # slack, whatsapp, telegram
user_id: str
channel_id: str
thread_id: str
text: str
message_type: MessageType
is_direct_message: bool
metadata: Dict[str, Any]
OutgoingMessage¶
@dataclass
class OutgoingMessage:
text: str
channel_id: str
thread_id: Optional[str]
message_type: MessageType
metadata: Dict[str, Any]
3. AdapterFactory¶
Gerencia criação e registro de adaptadores:
factory = AdapterFactory()
# Auto-detecção via env vars
factory.register_from_env()
# Ou manual
factory.register_from_config("slack", {
"bot_token": "...",
"signing_secret": "..."
})
# Buscar adapter
slack = factory.get_adapter("slack")
all_adapters = factory.get_all_adapters()
4. ConversationProcessor¶
Processa conversas de forma agnóstica à plataforma:
processor = ConversationProcessor(runner)
# Processa mensagem de qualquer plataforma
await processor.process_message(
message=incoming_message,
adapter=slack_adapter
)
5. Unified Bot (unified_bot.py)¶
Bot unificado com webhook único que roteia para o adapter correto:
@app.post("/webhook/{path:path}")
async def unified_webhook(path: str, request: Request):
# Encontra adapter baseado no path
adapter = factory.get_adapter_by_webhook_path(f"/{path}")
# Parse mensagem
message = await adapter.parse_webhook_request(request)
# Processa
await processor.process_message(message, adapter)
🚀 Como Usar¶
Configuração via Variáveis de Ambiente¶
# Plataformas ativas (auto-detectado se não especificado)
MESSAGING_PLATFORMS=slack,telegram,whatsapp
# Slack
SLACK_BOT_TOKEN=xoxb-...
SLACK_SIGNING_SECRET=...
# Telegram
TELEGRAM_BOT_TOKEN=123456:ABC-DEF...
# WhatsApp (via Twilio)
WHATSAPP_ACCOUNT_SID=AC...
WHATSAPP_AUTH_TOKEN=...
WHATSAPP_NUMBER=+5511999999999
# Backends (session e memory)
SESSION_BACKEND=redis
MEMORY_BACKEND=cloudsql
Executar o Bot Unificado¶
python unified_bot.py
Webhooks¶
Configure os webhooks nas plataformas:
- Slack:
https://seu-dominio.com/webhook/slack/events - Telegram:
https://seu-dominio.com/webhook/telegram/webhook - WhatsApp:
https://seu-dominio.com/webhook/whatsapp/webhook
➕ Adicionando Nova Plataforma¶
Exemplo: Discord¶
- Criar adapter em
messaging/adapters/discord_adapter.py:
from ..base import MessagingAdapter, IncomingMessage, OutgoingMessage
class DiscordAdapter(MessagingAdapter):
def __init__(self, config):
super().__init__(config)
self.bot = None
def _validate_config(self):
if "bot_token" not in self.config:
raise ValueError("Discord bot_token required")
async def setup(self):
# Inicializa cliente Discord
self.bot = discord.Client(token=self.config["bot_token"])
await self.bot.start()
async def parse_webhook_request(self, request):
# Parse Discord webhook/gateway event
data = await request.json()
# ... converter para IncomingMessage
return IncomingMessage(...)
async def send_message(self, message):
# Enviar via Discord API
channel = await self.bot.fetch_channel(message.channel_id)
await channel.send(message.text)
return True
# ... implementar outros métodos abstratos
@property
def platform_name(self):
return "discord"
@property
def webhook_path(self):
return "/discord/webhook"
- Registrar no factory em
messaging/factory.py:
from .adapters.discord_adapter import DiscordAdapter
class AdapterFactory:
ADAPTER_CLASSES = {
"slack": SlackAdapter,
"whatsapp": WhatsAppAdapter,
"telegram": TelegramAdapter,
"discord": DiscordAdapter, # ← Adicionar aqui
}
def _get_platform_config(self, platform):
# ... código existente ...
elif platform == "discord":
token = os.getenv("DISCORD_BOT_TOKEN")
if token:
return {"bot_token": token}
- Configurar env vars:
MESSAGING_PLATFORMS=slack,discord
DISCORD_BOT_TOKEN=...
- Pronto! O bot já suporta Discord 🎉
🔄 Migração do Código Antigo¶
Opção 1: Substituir Completamente¶
Renomeie o arquivo antigo e use o novo:
mv slack_bot.py slack_bot.old.py
cp unified_bot.py slack_bot.py
Opção 2: Manter Ambos¶
Rode em portas diferentes:
# Bot legado (apenas Slack)
PORT=8080 python slack_bot.py
# Bot unificado (todas plataformas)
PORT=8081 python unified_bot.py
Opção 3: Migração Gradual¶
- Mantenha
slack_bot.pyem produção - Teste
unified_bot.pyem staging - Quando estável, redirecione webhook do Slack
- Desative
slack_bot.py
📊 Comparação¶
| Aspecto | Antes (slack_bot.py) | Depois (unified_bot.py) |
|---|---|---|
| Plataformas | Apenas Slack | Múltiplas (extensível) |
| Webhooks | 1 por plataforma | 1 webhook unificado |
| Código duplicado | Muito | Zero |
| Adicionar plataforma | Reescrever bot inteiro | Criar 1 adapter (50-100 linhas) |
| Manutenção | Difícil (código entrelaçado) | Fácil (separação clara) |
| Testes | Difícil | Fácil (mock adapters) |
🧪 Testes¶
Testar Adapter Isoladamente¶
from messaging.adapters import SlackAdapter
adapter = SlackAdapter({
"bot_token": "...",
"signing_secret": "..."
})
await adapter.setup()
# Simular mensagem
message = OutgoingMessage(
text="Hello",
channel_id="C123",
)
success = await adapter.send_message(message)
Testar Processor¶
processor = ConversationProcessor(runner)
incoming = IncomingMessage(
platform="slack",
user_id="U123",
channel_id="C123",
thread_id="123.456",
text="Olá",
)
await processor.process_message(incoming, slack_adapter)
🔧 Troubleshooting¶
Problema: Adapter não encontrado¶
Causa: Plataforma não registrada ou env vars incorretas
Solução:
# Verificar env vars
echo $MESSAGING_PLATFORMS
echo $SLACK_BOT_TOKEN
# Verificar logs
# Deve aparecer: "✅ Plataformas registradas: slack, telegram"
Problema: Webhook retorna 404¶
Causa: Path do webhook incorreto
Solução:
- Verificar webhook_path no adapter
- Garantir que começa com / correto
- Exemplo: /slack/events (não slack/events)
Problema: Mensagens duplicadas¶
Causa: Múltiplos bots rodando ou retry não filtrado
Solução:
- Verificar filtro de retries no parse_webhook_request
- Garantir apenas 1 instância do bot rodando
📈 Próximos Passos¶
- ✅ Refatoração completa
- ⬜ Adicionar testes unitários
- ⬜ Adicionar rate limiting
- ⬜ Implementar métricas (Prometheus)
- ⬜ Adicionar suporte a attachments (imagens, arquivos)
- ⬜ Implementar rich messages (buttons, cards)
- ⬜ Cache de respostas
📚 Recursos¶
🤝 Contribuindo¶
Para adicionar nova plataforma:
- Crie adapter em
messaging/adapters/ - Implemente interface
MessagingAdapter - Registre no
AdapterFactory.ADAPTER_CLASSES - Adicione env vars no
_get_platform_config - Documente no README
- Adicione testes
Desenvolvido com ❤️ para extensibilidade e manutenibilidade