Skip to content

Stack Tecnológica

Esta página detalha todas as tecnologias utilizadas no CSGOFlip e justifica cada escolha comparando com alternativas.

Visão Geral

CamadaTecnologiaVersão
RuntimeNode.js18+
LinguagemTypeScript5.3+
Framework BackendNestJS10+
HTTP AdapterFastify4+
Banco de DadosPostgreSQL14+
ORMPrisma5+
Cache/Pub-SubRedis7+
FilasBullMQ4+
WebSocketSocket.io4+
FrontendNext.js14+
AdminNext.js + shadcn/ui16+
StylingTailwind CSS3.4+
AnimaçõesFramer Motion11+

Backend

NestJS vs Express Puro

AspectoNestJSExpress Puro
Estrutura✅ Opinativo, organizado❌ Livre demais, vira bagunça
DI (Dependency Injection)✅ Nativo, poderoso❌ Precisa de lib externa
TypeScript✅ First-class support⚠️ Funciona, mas não é ideal
Decorators✅ @Controller, @Get, etc❌ Não tem
Módulos✅ Sistema de módulos❌ Não tem conceito
Validação✅ class-validator integrado❌ Precisa configurar
Documentação✅ Swagger automático❌ Manual
Guards/Pipes✅ Nativos❌ Middleware genérico
Testing✅ Jest integrado⚠️ Configuração manual
WebSocket✅ Gateway nativo❌ Precisa de lib
Microservices✅ Suporte nativo❌ Não tem

Por que NestJS?

Para um sistema de gambling com 17 módulos e 95+ use cases, precisamos de organização forçada. Express deixaria o código virar espaguete. NestJS nos dá arquitetura enterprise-grade com DI, módulos e decorators.

Fastify vs Express Adapter

AspectoFastifyExpress
Performance✅ ~2x mais rápido❌ Mais lento
JSON Parsing✅ Otimizado❌ body-parser necessário
Schema Validation✅ JSON Schema nativo❌ Precisa de lib
Logging✅ Pino integrado❌ Morgan/Winston manual
Hooks✅ Lifecycle hooks⚠️ Middleware chain
TypeScript✅ Tipos incluídos⚠️ @types necessário

Por que Fastify?

Em um sistema de gambling com milhares de requisições por segundo, performance importa. Fastify é ~2x mais rápido que Express, especialmente em JSON parsing.

typescript
// main.ts - Configuração do Fastify
const app = await NestFactory.create<NestFastifyApplication>(
  AppModule,
  new FastifyAdapter({ logger: true }),
);

Banco de Dados

PostgreSQL vs MongoDB

AspectoPostgreSQLMongoDB
Transações ACID✅ Completo⚠️ Limitado
Relacionamentos✅ Foreign Keys❌ Referências manuais
Consistência✅ Strong consistency⚠️ Eventual consistency
Queries Complexas✅ SQL poderoso⚠️ Aggregation pipeline
Integridade✅ Constraints nativos❌ Validação na app
Auditoria✅ Triggers, extensions⚠️ Change streams
Particionamento✅ Nativo✅ Sharding
Full-Text Search✅ Nativo✅ Atlas Search

Por que PostgreSQL?

Sistema financeiro EXIGE transações ACID e integridade referencial. MongoDB não garante consistência em operações financeiras complexas. Double-entry bookkeeping precisa de constraints de banco.

sql
-- Exemplo: Constraint que garante integridade financeira
ALTER TABLE transactions
ADD CONSTRAINT check_transaction_integrity
CHECK (
  (type = 'DEBIT' AND amount_cents < 0) OR
  (type = 'CREDIT' AND amount_cents > 0)
);

Prisma vs TypeORM

AspectoPrismaTypeORM
Type Safety✅ 100% type-safe⚠️ Decorators podem falhar
Schema✅ prisma.schema declarativo❌ Decorators no código
Migrations✅ Automáticas⚠️ Manuais ou sync
Performance✅ Query engine otimizado⚠️ ORM overhead
Relations✅ Fácil de definir⚠️ Decorators verbosos
Raw Queries✅ $queryRaw✅ query()
Studio✅ Prisma Studio❌ Não tem

Por que Prisma?

Type-safety absoluto. Quando você muda o schema, o TypeScript quebra em todos os lugares que precisam ser atualizados. TypeORM usa decorators que podem ter tipos errados silenciosamente.

prisma
// schema.prisma - Declarativo e limpo
model User {
  id          BigInt    @id @default(autoincrement())
  steamId     String    @unique
  username    String
  balanceCents BigInt   @default(0)
  
  transactions Transaction[]
  inventory    UserInventory[]
  battles      BattlePlayer[]
}

Cache e Mensageria

Redis vs Memcached

AspectoRedisMemcached
Estruturas✅ Strings, Lists, Sets, Hashes, Sorted Sets❌ Apenas strings
Pub/Sub✅ Nativo❌ Não tem
Persistência✅ RDB/AOF❌ Apenas memória
Lua Scripts✅ Suporta❌ Não tem
Cluster✅ Redis Cluster⚠️ Consistent hashing
Distributed Locks✅ Redlock❌ Não tem

Por que Redis?

Precisamos de Pub/Sub para WebSocket scaling, Distributed Locks para race conditions financeiras, e estruturas de dados complexas (sorted sets para leaderboards). Memcached é simples demais.

typescript
// Uso de Redis para múltiplos propósitos
// 1. Sessions
await redis.set(`session:${sessionId}`, JSON.stringify(session), 'EX', 604800);

// 2. Cache
await redis.set(`case:${caseId}`, JSON.stringify(caseData), 'EX', 3600);

// 3. Distributed Lock
const lock = await redlock.acquire([`lock:user:${userId}`], 5000);

// 4. Pub/Sub para WebSocket
redis.publish('balance:update', JSON.stringify({ userId, balance }));

BullMQ vs Bull vs Agenda

AspectoBullMQBullAgenda
TypeScript✅ Nativo⚠️ @types❌ Fraco
Performance✅ Melhor⚠️ Bom❌ MongoDB overhead
Retry✅ Avançado✅ Bom⚠️ Básico
Rate Limiting✅ Nativo⚠️ Plugin❌ Manual
Concurrency✅ Por worker✅ Por worker⚠️ Limitado
Dashboard✅ Bull Board✅ Arena❌ Não tem

Por que BullMQ?

É a evolução do Bull com melhor suporte a TypeScript e performance. Agenda usa MongoDB, que não queremos adicionar ao stack.


WebSocket

Socket.io vs WS Puro vs Pusher

AspectoSocket.ioWS PuroPusher
Fallback✅ Polling automático❌ Apenas WS✅ Gerenciado
Rooms✅ Nativo❌ Manual✅ Channels
Reconnect✅ Automático❌ Manual✅ Gerenciado
Scaling✅ Redis Adapter❌ Sticky sessions✅ Gerenciado
Custo✅ Gratuito✅ Gratuito❌ Pago
Controle✅ Total✅ Total❌ Limitado

Por que Socket.io?

Rooms nativas para batalhas e usuários, reconnect automático para mobile, Redis Adapter para escalar horizontalmente. WS puro precisaria de muito código extra.

typescript
// WebSocket Gateway com Socket.io
@WebSocketGateway({ cors: true })
export class WebSocketGateway {
  @WebSocketServer()
  server: Server;

  // Sala por usuário (privada)
  handleConnection(client: Socket) {
    client.join(`user:${userId}`);
  }

  // Broadcast para sala de batalha
  emitBattleUpdate(battleId: bigint, data: any) {
    this.server.to(`battle:${battleId}`).emit('battle:update', data);
  }

  // Broadcast global (live drops)
  emitLiveDrop(drop: LiveDropDto) {
    this.server.emit('live:drop', drop);
  }
}

Frontend

Next.js vs React + Vite vs Remix

AspectoNext.jsReact + ViteRemix
SSR✅ Nativo❌ Manual✅ Nativo
SSG✅ Nativo❌ Manual⚠️ Limitado
Routing✅ File-based❌ React Router✅ File-based
API Routes✅ Nativo❌ Não tem✅ Loaders
Image Optimization✅ next/image❌ Manual❌ Manual
Ecosystem✅ Maior✅ Grande⚠️ Crescendo
Vercel Deploy✅ 1 click⚠️ Configuração⚠️ Configuração

Por que Next.js?

App Router do Next.js 14 é perfeito para nossa arquitetura. Server Components para páginas estáticas (caixas), Client Components para interação (roleta). Deploy no Vercel em 1 click.

Tailwind CSS vs CSS Modules vs Styled Components

AspectoTailwindCSS ModulesStyled Components
Bundle Size✅ Purged (pequeno)✅ Pequeno❌ Runtime JS
DX✅ Muito rápido⚠️ Arquivos extras✅ Colocalizado
Consistência✅ Design system forçado❌ Livre❌ Livre
Responsivo✅ Classes (sm:, md:)❌ Media queries❌ Media queries
Dark Mode✅ dark: prefix❌ Manual❌ ThemeProvider
Animações✅ animate-*❌ Manual⚠️ Keyframes

Por que Tailwind?

Velocidade de desenvolvimento incomparável. Responsivo, dark mode, hover states - tudo com classes. Combinado com Framer Motion para animações complexas da roleta.


Autenticação

Sessions (Redis) vs JWT vs OAuth Tokens

AspectoSessionsJWTOAuth Tokens
Revogação✅ Instantânea❌ Blacklist necessária⚠️ Refresh token
Tamanho✅ ID pequeno❌ Payload grande⚠️ Médio
Stateless❌ Precisa de storage✅ Stateless⚠️ Refresh precisa
Multi-device✅ Lista de sessões❌ Difícil controlar⚠️ Complexo
Logout All✅ Simples❌ Muito difícil⚠️ Complexo

Por que Sessions?

Em gambling, precisamos poder banir usuários instantaneamente. Com JWT, o token continua válido até expirar. Com sessions Redis, deletamos a sessão e acabou.

typescript
// Banir usuário = deletar todas as sessões
async banUser(userId: bigint) {
  // 1. Marca no banco
  await this.userRepo.update(userId, { status: 'BANNED' });
  
  // 2. Remove TODAS as sessões (logout instantâneo)
  const sessionKeys = await this.redis.keys(`session:*:${userId}`);
  await this.redis.del(...sessionKeys);
  
  // Usuário é desconectado imediatamente, não importa
  // quantos dispositivos esteja usando
}

IDs

Snowflake vs UUID vs Auto-increment

AspectoSnowflakeUUIDAuto-increment
Tamanho✅ 8 bytes (BigInt)❌ 16 bytes✅ 4-8 bytes
Ordenação✅ Por tempo❌ Aleatório✅ Sequencial
Distribuído✅ Sem coordenação✅ Sem coordenação❌ Precisa de sequência
Index Performance✅ Excelente❌ Fragmentação✅ Excelente
Previsibilidade⚠️ Timestamp visível✅ Completamente aleatório❌ Sequencial

Por que Snowflake?

Performance de índice igual a auto-increment, geração distribuída sem coordenação central, e ordenação natural por timestamp. UUID fragmenta índices B-tree.

typescript
// Estrutura do Snowflake ID (64 bits)
// | Timestamp (41 bits) | Worker ID (10 bits) | Sequence (12 bits) |

class SnowflakeService {
  private sequence = 0n;
  private lastTimestamp = -1n;
  
  generate(): bigint {
    let timestamp = BigInt(Date.now());
    
    if (timestamp === this.lastTimestamp) {
      this.sequence = (this.sequence + 1n) & 0xFFFn; // 12 bits
      if (this.sequence === 0n) {
        timestamp = this.waitNextMillis(timestamp);
      }
    } else {
      this.sequence = 0n;
    }
    
    this.lastTimestamp = timestamp;
    
    return ((timestamp - EPOCH) << 22n) |
           (this.workerId << 12n) |
           this.sequence;
  }
}

Valores Monetários

BigInt (centavos) vs Decimal vs Float

AspectoBigInt (centavos)DecimalFloat
Precisão✅ Exata✅ Exata❌ Impreciso
Operações atômicas✅ Simples⚠️ Biblioteca❌ Problemático
Performance✅ Nativo⚠️ Overhead✅ Nativo
Comparação✅ Direta⚠️ Método❌ Problemático
Armazenamento✅ BIGINT✅ DECIMAL❌ Não recomendado

NUNCA use Float para dinheiro!

javascript
0.1 + 0.2 === 0.3 // false! Resultado: 0.30000000000000004

Por que BigInt em centavos?

Zero imprecisão. R$ 10,50 = 1050 centavos. Operações são simples somas/subtrações de inteiros. Não precisa de biblioteca de precisão arbitrária.

typescript
// Conversões
function toCents(reais: number): bigint {
  return BigInt(Math.round(reais * 100));
}

function toReais(cents: bigint): number {
  return Number(cents) / 100;
}

// Uso
const price = 1050n; // R$ 10,50
const balance = 5000n; // R$ 50,00
const remaining = balance - price; // 3950n = R$ 39,50

Resumo das Escolhas

DecisãoEscolhaMotivo Principal
FrameworkNestJSOrganização forçada, DI nativo
HTTPFastify2x mais rápido
BancoPostgreSQLACID para financeiro
ORMPrismaType-safety absoluto
CacheRedisEstruturas + Pub/Sub + Locks
FilasBullMQTypeScript + Performance
WebSocketSocket.ioRooms + Reconnect + Adapter
FrontendNext.js 14SSR + App Router
CSSTailwindVelocidade de dev
AuthSessionsRevogação instantânea
IDsSnowflakePerformance + Distribuído
DinheiroBigInt (cents)Precisão absoluta

Cada escolha foi feita pensando em segurança, performance e manutenibilidade para um sistema de gambling em produção.

Documentação Técnica CSGOFlip