Arquitetura do Sistema
O CSGOFlip segue os princípios de Clean Architecture, garantindo separação de responsabilidades, testabilidade e manutenibilidade.
Por que Clean Architecture?
Benefícios
- Independência de frameworks: A lógica de negócio não depende do NestJS
- Testabilidade: Use cases podem ser testados sem banco de dados ou HTTP
- Independência de UI: O mesmo backend serve web, mobile e admin
- Independência de banco: Trocar de PostgreSQL para outro DB é possível
- Independência de agentes externos: APIs externas são abstraídas
Diagrama de Camadas
┌─────────────────────────────────────────────────────────────────────┐
│ PRESENTATION LAYER │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────────┐ │
│ │ Controllers │ │ Guards │ │ Pipes │ │ Modules │ │
│ │ (HTTP) │ │ (Auth) │ │ (Validate) │ │ (NestJS) │ │
│ └──────┬──────┘ └─────────────┘ └─────────────┘ └────────────┘ │
│ │ │
│ │ DTOs (Request/Response) │
└─────────┼────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ APPLICATION LAYER │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ USE CASES │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ │
│ │ │ OpenCase │ │ CreateBattle │ │ ProcessWithdrawal │ │ │
│ │ │ UseCase │ │ UseCase │ │ UseCase │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ APPLICATION SERVICES │ │
│ │ ┌────────────────┐ ┌────────────────┐ ┌─────────────────┐ │ │
│ │ │ Transaction │ │ ProvablyFair │ │ Notification │ │ │
│ │ │ Service │ │ Service │ │ Service │ │ │
│ │ └────────────────┘ └────────────────┘ └─────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ Interfaces (Repository Contracts) │
└─────────┼────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ DOMAIN LAYER │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ENTITIES │ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌──────────┐ │ │
│ │ │ User │ │ Case │ │ Battle │ │ Item │ │Transaction│ │ │
│ │ └────────┘ └────────┘ └────────┘ └────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ REPOSITORY INTERFACES │ │
│ │ ┌────────────────┐ ┌────────────────┐ ┌─────────────────┐ │ │
│ │ │ IUserRepo │ │ ICaseRepo │ │ IBattleRepo │ │ │
│ │ └────────────────┘ └────────────────┘ └─────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ INFRASTRUCTURE LAYER │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Database │ │ Redis │ │ External APIs │ │
│ │ Repositories │ │ Services │ │ Services │ │
│ │ (Prisma) │ │ (Cache/Lock) │ │ (Steam/Payment) │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ WebSocket │ │ Auth │ │ Audit │ │
│ │ Gateway │ │ Services │ │ Service │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘Descrição das Camadas
1. Presentation Layer (Camada de Apresentação)
Responsabilidade: Receber requisições HTTP, validar entrada, e retornar respostas.
Componentes:
- Controllers: Endpoints HTTP que delegam para Use Cases
- Guards: Autenticação e autorização
- Pipes: Validação e transformação de dados
- Modules: Organização e injeção de dependências NestJS
- DTOs: Data Transfer Objects para request/response
Regras:
- ❌ NÃO contém lógica de negócio
- ❌ NÃO acessa banco de dados diretamente
- ✅ Apenas orquestra chamadas para Use Cases
- ✅ Trata erros HTTP e formata respostas
2. Application Layer (Camada de Aplicação)
Responsabilidade: Orquestrar a lógica de negócio através de Use Cases.
Componentes:
- Use Cases: Um caso de uso por operação de negócio
- Application Services: Serviços compartilhados entre use cases
- Interfaces: Contratos para repositórios e serviços externos
Regras:
- ✅ Contém a lógica de negócio da aplicação
- ✅ Coordena múltiplos repositórios/serviços
- ❌ NÃO conhece HTTP, WebSocket ou detalhes de framework
- ❌ NÃO depende de implementações concretas
3. Domain Layer (Camada de Domínio)
Responsabilidade: Definir entidades e regras de negócio puras.
Componentes:
- Entities: Modelos de dados do domínio
- Repository Interfaces: Contratos de acesso a dados
- Domain Services: Lógica de domínio complexa (raro)
Regras:
- ✅ Camada mais interna e estável
- ✅ Zero dependências externas
- ❌ NÃO conhece framework, banco de dados ou HTTP
- ❌ NÃO muda por motivos técnicos
4. Infrastructure Layer (Camada de Infraestrutura)
Responsabilidade: Implementar detalhes técnicos e integrações.
Componentes:
- Repositories: Implementação Prisma dos repositórios
- External APIs: Integrações (Steam, pagamentos, S3)
- Cache/Queue: Redis, BullMQ
- Auth: Serviços de autenticação
- WebSocket: Gateway Socket.io
Regras:
- ✅ Implementa interfaces da camada de domínio
- ✅ Contém código específico de tecnologia
- ❌ NÃO contém lógica de negócio
- ❌ Lógica de negócio NÃO deve depender desta camada
Fluxo de uma Requisição
Estrutura de Pastas
src/
├── domain/ # Camada de Domínio
│ ├── entities/ # Entidades do domínio
│ │ ├── user.entity.ts
│ │ ├── case.entity.ts
│ │ ├── battle.entity.ts
│ │ └── ...
│ └── repositories/ # Interfaces dos repositórios
│ ├── user.repository.interface.ts
│ ├── case.repository.interface.ts
│ └── ...
│
├── application/ # Camada de Aplicação
│ ├── use-cases/ # Casos de uso
│ │ ├── case-opening/
│ │ │ ├── open-case.use-case.ts
│ │ │ └── verify-opening.use-case.ts
│ │ ├── battle/
│ │ │ ├── create-battle.use-case.ts
│ │ │ └── join-battle.use-case.ts
│ │ └── ...
│ ├── services/ # Serviços de aplicação
│ │ ├── transaction.service.ts
│ │ ├── provably-fair.service.ts
│ │ └── ...
│ ├── dto/ # Data Transfer Objects
│ │ ├── auth.dto.ts
│ │ ├── case.dto.ts
│ │ └── ...
│ └── interfaces/ # Interfaces de serviços
│ ├── payment.interface.ts
│ └── storage.interface.ts
│
├── infrastructure/ # Camada de Infraestrutura
│ ├── database/
│ │ └── repositories/ # Implementações Prisma
│ │ ├── user.repository.ts
│ │ ├── case.repository.ts
│ │ └── ...
│ ├── auth/ # Autenticação
│ │ ├── session.service.ts
│ │ └── steam-oauth.service.ts
│ ├── redis/ # Cache e locks
│ │ ├── redis.service.ts
│ │ └── lock.service.ts
│ ├── websocket/ # Tempo real
│ │ └── websocket.gateway.ts
│ └── external-apis/ # APIs externas
│ ├── steam.service.ts
│ └── payment.service.ts
│
└── presentation/ # Camada de Apresentação
├── controllers/ # Controllers HTTP
│ ├── auth.controller.ts
│ ├── case.controller.ts
│ └── ...
├── guards/ # Guards de autenticação
│ ├── auth.guard.ts
│ └── admin.guard.ts
├── pipes/ # Pipes de validação
│ └── validation.pipe.ts
└── modules/ # Módulos NestJS
├── auth.module.ts
├── case.module.ts
└── ...Injeção de Dependências
O NestJS gerencia a injeção de dependências. Repositórios são injetados através de tokens:
// Definição do token
export const USER_REPOSITORY = 'USER_REPOSITORY';
// No módulo
@Module({
providers: [
{
provide: USER_REPOSITORY,
useClass: UserRepository,
},
GetUserUseCase,
],
})
export class UserModule {}
// No use case
@Injectable()
export class GetUserUseCase {
constructor(
@Inject(USER_REPOSITORY)
private readonly userRepository: IUserRepository,
) {}
}Benefícios Práticos
1. Testabilidade
// Use case pode ser testado com mock do repositório
describe('OpenCaseUseCase', () => {
it('should open case and return item', async () => {
const mockRepo = { findById: jest.fn().mockResolvedValue(mockCase) };
const useCase = new OpenCaseUseCase(mockRepo);
const result = await useCase.execute(userId, caseId);
expect(result.item).toBeDefined();
});
});2. Substituibilidade
// Trocar implementação sem alterar lógica de negócio
@Module({
providers: [
{
provide: PAYMENT_PROVIDER,
useClass: process.env.PAYMENT === 'stripe'
? StripeProvider
: PaagProvider,
},
],
})3. Organização Clara
Cada desenvolvedor sabe exatamente onde colocar cada tipo de código:
- Endpoint novo? → Controller
- Lógica de negócio? → Use Case
- Query no banco? → Repository
- Integração externa? → Infrastructure Service
Anti-patterns Evitados
O que NÃO fazer
1. Lógica de negócio no Controller
// ❌ ERRADO
@Post('open')
async openCase() {
const roll = Math.random(); // Lógica no controller!
if (roll > 0.5) { ... }
}
// ✅ CORRETO
@Post('open')
async openCase() {
return this.openCaseUseCase.execute(userId, caseId);
}2. Use Case acessando Request/Response HTTP
// ❌ ERRADO
class OpenCaseUseCase {
execute(req: Request, res: Response) { ... }
}
// ✅ CORRETO
class OpenCaseUseCase {
execute(userId: bigint, caseId: bigint) { ... }
}3. Repository com lógica de negócio
// ❌ ERRADO
class UserRepository {
async createWithBonus(data) {
const bonus = data.isNew ? 100 : 0; // Lógica no repo!
return this.prisma.user.create({ ...data, balance: bonus });
}
}
// ✅ CORRETO - Lógica fica no Use Case
class CreateUserUseCase {
execute(data) {
const bonus = data.isNew ? 100 : 0;
return this.userRepo.create({ ...data, balance: bonus });
}
}Arquivos Fonte Relacionados
Principais Arquivos
src/app.module.ts- Módulo raiz com imports de todas as camadassrc/domain/- Entidades e interfacessrc/application/use-cases/- Todos os use casessrc/infrastructure/database/repositories/- Implementações dos repositóriossrc/presentation/controllers/- Todos os controllers
