O Doc Flow é um backend para processamento e conversão assíncrona de documentos, estruturado como monólito modular orientado a mensageria, com comunicação desacoplada entre API e workers e notificações em tempo real via WebSocket.
O foco do projeto é demonstrar:
- Separação rigorosa de responsabilidades
- Processamento assíncrono correto
- Isolamento por cliente sem autenticação
- Controle explícito de recursos
- Efemeridade como regra arquitetural
O sistema não é uma plataforma de gestão documental. É um motor técnico de conversão previsível e observável.
O funcionamento do sistema depende obrigatoriamente da configuração correta do arquivo
.env.
Antes de executar localmente:
- Criar o
.enva partir do.env.example - Garantir o preenchimento correto de todas as variáveis
Itens críticos:
- URL do PostgreSQL
- URL do Redis
- URL do RabbitMQ
- Diretórios de storage
- Configuração de CORS (
ALLOWED_ORIGINS) - Configuração do Socket.IO
- Limites de upload e TTL
Falhas comuns decorrentes de má configuração:
- Workers não processam jobs
- Eventos não chegam via WebSocket
- Erros silenciosos de conexão
- Rate limit não funcional
- Falhas na persistência
Frontend (Next.js)
│
│ HTTP + WebSocket
▼
Flask API + Socket.IO
│
├── PostgreSQL (estado dos jobs)
│
├── RabbitMQ (fila de tarefas)
│
└── Redis (pub/sub + cache)
│
▼
Celery Workers
│
▼
Storage Local
- Flask
- Celery
- RabbitMQ
- Redis
- PostgreSQL
- Docker
O sistema não possui autenticação.
O isolamento é feito exclusivamente por:
client_id(UUID v4)- Cookie HTTP-only
- Validação de correspondência no download
- Isolamento físico de diretórios
- Limite de armazenamento por cliente
- Expiração automática
- 10 requisições por segundo por IP
- Implementado na camada HTTP
- Não há enumeração de jobs
- UUID evita previsibilidade
- Downloads exigem correspondência de
client_id
PENDINGPROCESSINGDONEFAILED
Tabela DocumentJob:
id (UUID)client_idstatusinput_filenameinput_pathoutput_formatoutput_patherror_messagecreated_atprocessed_atexpires_at
O banco representa apenas o ciclo de vida técnico.
- Upload via API
- Arquivo salvo em
/storage/input/{client_id} - Job persistido no banco
- Metadados enviados ao RabbitMQ
- Worker processa
- Output movido para
/storage/output/{client_id} - Status atualizado
- Evento publicado no Redis
- API emite evento via WebSocket
Arquivos nunca trafegam pela fila.
A fila transporta apenas metadados.
- Workers publicam eventos no Redis
- API consome via pub/sub
- Emissão via Socket.IO para
room(client_id)
Eventos:
job_processingjob_donejob_failed
Sem polling contínuo.
Estrutura física:
/storage/input/{client_id}
/storage/output/{client_id}
- 250 MB por
client_id - Soma de input + output
- Upload bloqueado quando limite atingido
- TTL padrão: 24h
- Tarefa periódica remove:
- Registros
- Arquivos físicos
- Libera cota
Efemeridade é comportamento padrão.
- CSV ↔ Excel ↔ JSON
pandas
- Markdown ↔ HTML ↔ TXT
markdown,markitdown
ReportLab,fpdf
pdfplumber,PyPDF2,tika
- DOCX / PPTX → PDF / Markdown
python-docx,docling
Conversões dependentes de layout visual complexo não fazem parte do escopo.
O projeto é totalmente containerizado utilizando Docker e orquestrado via Docker Compose, garantindo:
- Isolamento de dependências
- Reprodutibilidade do ambiente
- Inicialização previsível
- Execução uniforme entre ambientes
Todos os serviços são executados como containers independentes, comunicando-se exclusivamente via rede interna do Docker.
O docker-compose.yml define os seguintes serviços:
- Frontend (Next.js)
- API (Flask + Socket.IO)
- Worker (Celery)
- Celery Beat
- PostgreSQL
- Redis
- RabbitMQ
Arquiteturalmente:
- A API é responsável pelo HTTP e WebSocket.
- O Worker executa tarefas assíncronas.
- O Beat agenda tarefas periódicas (ex.: expiração de arquivos).
- O RabbitMQ atua como broker.
- O Redis é utilizado para pub/sub e backend de resultados.
- O PostgreSQL mantém o estado dos jobs.
Todos os serviços compartilham a mesma rede Docker interna.
Para subir todo o ambiente:
docker compose up --buildEsse comando:
- Constrói as imagens necessárias
- Cria a rede interna
- Inicializa todos os containers
- Conecta automaticamente os serviços pelos nomes definidos no compose
Após inicialização:
- API disponível em
http://localhost:4000 - Swagger disponível em
http://localhost:4000/docs/swagger - Frontend disponível em
http://localhost:3000
A comunicação entre serviços ocorre exclusivamente via hostname interno do Docker:
postgresredisrabbitmqapiworker
Exemplo de URL interna válida:
amqp://guest:guest@rabbitmq:5672
redis://redis:6379/0
postgresql+psycopg://user:pass@postgres:5432/db
Não há dependência de localhost dentro dos containers.
Os serviços utilizam:
depends_on- Inicialização controlada por ordem lógica
No entanto:
A disponibilidade real é garantida por retry interno (ex.: Celery e SQLAlchemy), não apenas por dependência declarativa.
Isso evita falhas prematuras caso algum serviço ainda esteja inicializando.
Volumes são utilizados para:
- Persistência do PostgreSQL
- Persistência do RabbitMQ
- Diretório
/storage(input/output de arquivos)
Isso garante que:
- Jobs não sejam perdidos em restart
- Arquivos persistam até expiração
- Broker mantenha estado de filas
- Alterações em código exigem rebuild (
--build) - Alterações apenas no
docker-compose.ymlnão exigem rebuild - Containers são stateless (exceto serviços com volume)
Logs podem ser inspecionados via:
docker compose logs -fOu por serviço específico:
docker compose logs -f worker- SQLAlchemy 2.0
- Alembic
synchronizenão utilizado- Migrations explícitas e versionadas
As migrations podem ser executadas manualmente dentro do container da API:
docker compose exec api alembic upgrade headO banco é único, com separação lógica por domínio.
Pré-requisitos:
- Docker
- Docker Compose
- Arquivo
.envconfigurado corretamente
Subida do ambiente:
docker compose up --buildParada:
docker compose downParada removendo volumes:
docker compose down -vA API está documentada via:
- flask-smorest
- marshmallow
- Swagger UI
http://localhost:4000/docs/swagger
- Todas as rotas HTTP
- Schemas de request/response
- Parâmetros de rota
- Status codes
- Validações
O Swagger representa o contrato real da API.
Não documenta eventos internos de mensageria.
Serviços orquestrados via Docker Compose:
- Web
- API
- Worker
- Celery Beat
- PostgreSQL
- Redis
- RabbitMQ
Execução:
docker compose up --build- SQLAlchemy 2.0
- Alembic
synchronizenão utilizado- Migrations explícitas
Banco único com separação lógica por domínio.
backend
poetry install
poetry run start-api
poetry run start-worker
poetry run celery -A src.app.workers.celery_app beat --loglevel=infofrontend
npm run devPré-requisitos:
- Python 3.11+
- PostgreSQL
- Redis
- RabbitMQ
.envconfigurado
- Monólito modular (evita complexidade prematura)
- Mensageria para desacoplamento
- UUID para evitar enumeração
- TTL obrigatório
- Storage isolado por cliente
- WebSocket fora do fluxo HTTP
- Arquivos fora da fila
- Sem autenticação (decisão consciente)
- Limite rígido de 250 MB
- Conversões complexas fora do escopo
- Observabilidade básica (logging estruturado)
- Retry + DLQ no RabbitMQ
- Cache Redis para consultas frequentes
- Observabilidade avançada (OpenTelemetry)
- Testes E2E
- Storage externo (S3-compatible)
- Métricas Prometheus
Demonstrar como um sistema real de processamento de documentos deve ser estruturado para:
- Escalar horizontalmente
- Controlar recursos
- Evitar acoplamento
- Manter previsibilidade operacional
- Operar com efemeridade como padrão
O foco é engenharia sólida, não expansão indiscriminada de funcionalidades.