Jun 2024 - Jan 2026Built in PublicEncerrado

Shortly

Um encurtador de links com painel de analytics e insights em tempo real. Funciona com sistema de coins — o usuário compra créditos e usa conforme cria e monitora seus links.

PythonDjangoCeleryRedisPostgreSQLRabbitMQWebSocketDockerNginxS3EC2Mercado PagoHashidsTailwindCSSFlower
Shortly banner
400+Commits
1 anode desenvolvimento
10apps Django
8serviços no Docker
Arquitetura

Como o sistema é estruturado

Infraestrutura Docker

Nginx roteia /ws/* para Daphne (WebSocket) e /* para Gunicorn (HTTP) — dois servidores independentes no mesmo host.
nginxReverse proxy, static/media
:8080
shortlyDjango / Gunicorn (HTTP)
:8000
shortly_asgiDaphne (WebSocket)
:8001
postgresBanco de dados principal
:5432
redisCache, broker, channel layer
:6380
celery_workerProcessamento assíncrono
celery_beatAgendador de tarefas
flowerMonitoramento do Celery
:5556

Monolito modular — 10 apps

Cada app é responsável pelo seu próprio domínio. Comunicação entre apps via signals Django ou importação direta de services. Lógica de negócio em classes *Service, views finas.
converter/encurtamento de URL
billing/planos e pagamentos
account/autenticação
monitor/analytics
manager/painel WebSocket
security/brute force
notification/outbox de e-mails
toggler/feature flags
profiles/perfil público
common/abstrações base
Interface

Capturas de Tela

sh0rtly.com/dashboard
Painel Administrativo
Painel Administrativo
sh0rtly.com/faq
Perguntas Frequentes
Perguntas Frequentes
Desafios técnicos

Problemas reais, soluções concretas

01

Race condition em geração de short code

Em alta concorrência, múltiplas requisições poderiam ler o mesmo valor do contador e gerar o mesmo código. A solução usa select_for_update() combinado com F('value') + 1 dentro de uma transação atômica — incremento no banco, não no Python. Dois workers nunca leem o mesmo valor. Geração O(1) sem colisões por design.
02

Integridade da carteira sob concorrência

Um usuário com saldo 5 fazendo duas compras simultâneas de 4 créditos poderia ter ambas aprovadas se as leituras ocorressem antes de qualquer escrita. WalletService.debit() serializa as operações com select_for_update() no objeto da carteira: a segunda transação espera e lê o saldo já atualizado pela primeira.
03

Idempotência em webhooks de pagamento

Mercado Pago pode entregar o mesmo evento mais de uma vez em falhas de rede — processar duplicatas creditaria o usuário múltiplas vezes. A solução combina verificação de assinatura HMAC-SHA256 antes de qualquer query, com constraint UNIQUE em external_reference. A segunda entrega retorna 200 sem agir.
04

Outbox pattern para e-mails sem perda

Chamar SMTP diretamente na view cria acoplamento e timeout visível ao usuário. O e-mail é salvo no banco como EmailOutbox(status=PENDING) na mesma transação da operação de negócio. Um task Celery processa a fila com skip_locked=True — múltiplos workers processam e-mails distintos sem contenção.

Quer ver o código?

Ver repositório no GitHub