Pular para conteúdo

Solução 07 - Repositories e Banco de Dados 🗄️

Navegação

← Exercício 07 | Próxima Solução →

🟢 Respostas Fáceis

1. Fundamentos - SQL

Resposta 1

SQL - Structured Query Language:

- **Significado**: **S**tructured **Q**uery **L**anguage (Linguagem de Consulta Estruturada)
- **Finalidade**: Linguagem padrão para **gerenciar e manipular** bancos de dados relacionais

**🎯 Para que serve:**
- **Consultar** dados (SELECT)
- **Inserir** novos registros (INSERT)
- **Atualizar** dados existentes (UPDATE)
- **Deletar** registros (DELETE)
- **Definir** estruturas de tabelas (DDL)
- **Controlar** acesso e permissões

**🌍 Universalidade:**
```mermaid
graph TD
    A[SQL Standard] --> B[MySQL]
    A --> C[PostgreSQL]
    A --> D[SQL Server]
    A --> E[Oracle]
    A --> F[SQLite]

    style A fill:#e3f2fd
```

2. CRUD - Comando INSERT

Resposta 2

Comando SQL para inserir produto:

```sql
INSERT INTO produtos (nome, preco)
VALUES ('Mouse', 50.00);
```

**📝 Variações mais completas:**
```sql
-- Com mais campos
INSERT INTO produtos (nome, preco, categoria, estoque, data_criacao)
VALUES ('Mouse', 50.00, 'informatica', 100, NOW());

-- Inserção múltipla
INSERT INTO produtos (nome, preco) VALUES
    ('Mouse', 50.00),
    ('Teclado', 150.00),
    ('Monitor', 800.00);

-- Retornando o ID criado (PostgreSQL)
INSERT INTO produtos (nome, preco)
VALUES ('Mouse', 50.00)
RETURNING id;
```

🟡 Respostas Médias

3. Primary Key vs Foreign Key

Resposta 3

Diferenciação PK vs FK:

| Aspecto | Primary Key (PK) | Foreign Key (FK) |
|---------|------------------|------------------|
| **Função** | **Identifica unicamente** cada registro | **Conecta** com outra tabela |
| **Valores** | **Únicos** e **não nulos** | Podem repetir e ser nulos |
| **Quantidade** | **1 por tabela** apenas | **Várias** por tabela |
| **Relacionamento** | **Lado "um"** (1:N) | **Lado "muitos"** (1:N) |

**🔗 Por que FK é essencial:**
- **Integridade referencial**: Garante que dados conectados existam
- **Consistência**: Evita registros "órfãos"
- **Relacionamentos**: Permite JOINs eficientes entre tabelas
- **Cascata**: Controla o que acontece quando PK é deletada

**📊 Exemplo Prático:**
```sql
-- Tabela PAI (lado "um")
CREATE TABLE categorias (
    id INT PRIMARY KEY,           -- ← PK
    nome VARCHAR(100)
);

-- Tabela FILHA (lado "muitos")
CREATE TABLE produtos (
    id INT PRIMARY KEY,           -- ← PK desta tabela
    nome VARCHAR(100),
    categoria_id INT,             -- ← FK para categorias
    FOREIGN KEY (categoria_id) REFERENCES categorias(id)
);
```

**⚡ Benefícios da FK:**
```mermaid
graph TD
    A[Foreign Key] --> B[Integridade Referencial]
    A --> C[Previne Dados Órfãos]
    A --> D[Suporte a JOINs]
    A --> E[Cascata de Operações]

    B --> F[Dados Sempre Consistentes]
    C --> G[Sem Produtos sem Categoria]
    D --> H[Consultas Relacionais Eficientes]
    E --> I[DELETE/UPDATE Automático]
```

4. Padrão Repository vs SQL Direto

Resposta 4

Por que usar Repository em vez de SQL direto no Service:

**❌ Problemas do SQL Direto no Service:**
```javascript
// ❌ Service conhece detalhes do banco
class UsuarioService {
    async buscar(id) {
        const result = await db.query(
            'SELECT * FROM usuarios WHERE id = ? AND deletado_em IS NULL',
            [id]
        );
        // Service precisa saber sobre colunas, SQL, etc.
        return result.rows[0];
    }
}
```

**✅ Benefícios do Repository:**
```javascript
// ✅ Service foca na regra, Repository no banco
class UsuarioService {
    async buscar(id) {
        const usuario = await this.usuarioRepository.buscarPorId(id);
        if (!usuario) {
            throw new UsuarioNaoEncontradoError(id);
        }
        return usuario;
    }
}

class UsuarioRepository {
    async buscarPorId(id) {
        const result = await this.db.query(
            'SELECT * FROM usuarios WHERE id = ? AND deletado_em IS NULL',
            [id]
        );
        return result.rows[0] ? this.mapearParaEntidade(result.rows[0]) : null;
    }
}
```

**🎯 Vantagens do Repository:**

1. **Isolamento de Responsabilidades**:
   - Service: Regras de negócio
   - Repository: Acesso a dados

2. **Testabilidade**:
   ```javascript
   // Fácil de mockar em testes
   const mockRepository = {
       buscarPorId: jest.fn().mockResolvedValue(usuarioFake)
   };
   ```

3. **Flexibilidade de Implementação**:
   ```javascript
   // Pode trocar MySQL por MongoDB sem afetar Service
   class UsuarioRepositoryMongo extends UsuarioRepository {
       async buscarPorId(id) {
           return await this.collection.findOne({ _id: id });
       }
   }
   ```

4. **Reutilização**:
   ```javascript
   // Múltiplos Services usam o mesmo Repository
   class UsuarioService { /* usa UsuarioRepository */ }
   class AuthService { /* usa UsuarioRepository */ }
   class RelatorioService { /* usa UsuarioRepository */ }
   ```

🔴 Resposta Desafio

5. Modelagem Real - Blog System

Resposta 5

Sistema Blog - Escritores e Artigos:

**a) Modelagem 1:N (Escritor → Artigos):**
```sql
-- Tabela ESCRITORES (lado "um")
CREATE TABLE escritores (
    id INT PRIMARY KEY AUTO_INCREMENT,
    nome VARCHAR(100) NOT NULL,
    email VARCHAR(150) UNIQUE NOT NULL,
    bio TEXT,
    data_cadastro TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Tabela ARTIGOS (lado "muitos")
CREATE TABLE artigos (
    id INT PRIMARY KEY AUTO_INCREMENT,
    titulo VARCHAR(200) NOT NULL,
    conteudo TEXT NOT NULL,
    resumo VARCHAR(500),
    escritor_id INT NOT NULL,               -- ← FK para escritores
    data_publicacao TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    status ENUM('rascunho', 'publicado', 'arquivado') DEFAULT 'rascunho',

    FOREIGN KEY (escritor_id) REFERENCES escritores(id)
        ON DELETE RESTRICT   -- Não permite deletar escritor com artigos
        ON UPDATE CASCADE    -- Atualiza FK se PK do escritor mudar
);
```

**b) Query SQL - Artigos do Autor ID 5:**
```sql
SELECT a.titulo
FROM artigos a
WHERE a.escritor_id = 5
  AND a.status = 'publicado'    -- Opcional: só publicados
ORDER BY a.data_publicacao DESC;
```

**📋 Queries Adicionais Úteis:**
```sql
-- Com informações do escritor (JOIN)
SELECT
    e.nome as escritor,
    a.titulo,
    a.data_publicacao
FROM artigos a
INNER JOIN escritores e ON a.escritor_id = e.id
WHERE e.id = 5
ORDER BY a.data_publicacao DESC;

-- Contagem de artigos por escritor
SELECT
    e.nome,
    COUNT(a.id) as total_artigos
FROM escritores e
LEFT JOIN artigos a ON e.id = a.escritor_id
WHERE e.id = 5
GROUP BY e.id, e.nome;
```

**c) Assinatura do ArtigoRepository:**
```typescript
interface ArtigoRepository {
    // Busca artigos por escritor
    buscarPorEscritorId(escritorId: number): Promise<Artigo[]>;

    // Versões mais específicas
    buscarPorEscritorIdPublicados(escritorId: number): Promise<Artigo[]>;
    buscarPorEscritorIdComPaginacao(
        escritorId: number,
        page: number,
        limit: number
    ): Promise<{ artigos: Artigo[], total: number }>;

    // Com filtros
    buscarPorEscritorIdComFiltros(
        escritorId: number,
        filtros: {
            status?: 'rascunho' | 'publicado' | 'arquivado';
            dataInicio?: Date;
            dataFim?: Date;
            termoBusca?: string;
        }
    ): Promise<Artigo[]>;
}
```

**🛠️ Implementação do Repository:**
```javascript
class ArtigoRepository {
    constructor(database) {
        this.db = database;
    }

    async buscarPorEscritorId(escritorId) {
        const query = `
            SELECT
                a.id,
                a.titulo,
                a.conteudo,
                a.resumo,
                a.data_publicacao,
                a.status,
                e.nome as escritor_nome
            FROM artigos a
            INNER JOIN escritores e ON a.escritor_id = e.id
            WHERE a.escritor_id = ?
            ORDER BY a.data_publicacao DESC
        `;

        const result = await this.db.query(query, [escritorId]);
        return result.rows.map(row => this.mapearParaEntidade(row));
    }

    async buscarPorEscritorIdPublicados(escritorId) {
        const query = `
            SELECT * FROM artigos
            WHERE escritor_id = ?
              AND status = 'publicado'
            ORDER BY data_publicacao DESC
        `;

        const result = await this.db.query(query, [escritorId]);
        return result.rows.map(row => this.mapearParaEntidade(row));
    }

    async buscarPorEscritorIdComPaginacao(escritorId, page = 1, limit = 10) {
        const offset = (page - 1) * limit;

        // Query para dados
        const queryDados = `
            SELECT * FROM artigos
            WHERE escritor_id = ?
            ORDER BY data_publicacao DESC
            LIMIT ? OFFSET ?
        `;

        // Query para contagem total
        const queryTotal = `
            SELECT COUNT(*) as total
            FROM artigos
            WHERE escritor_id = ?
        `;

        const [resultDados, resultTotal] = await Promise.all([
            this.db.query(queryDados, [escritorId, limit, offset]),
            this.db.query(queryTotal, [escritorId])
        ]);

        return {
            artigos: resultDados.rows.map(row => this.mapearParaEntidade(row)),
            total: resultTotal.rows[0].total,
            page,
            limit,
            totalPages: Math.ceil(resultTotal.rows[0].total / limit)
        };
    }

    // Mapeamento privado para entidade
    mapearParaEntidade(row) {
        return {
            id: row.id,
            titulo: row.titulo,
            conteudo: row.conteudo,
            resumo: row.resumo,
            escritorId: row.escritor_id,
            dataPublicacao: row.data_publicacao,
            status: row.status,
            // Computed properties
            url: `/artigos/${row.id}`,
            palavras: row.conteudo.split(' ').length
        };
    }
}
```

**📊 Diagrama do Relacionamento:**
```mermaid
erDiagram
    ESCRITORES {
        int id PK
        string nome
        string email UK
        text bio
        timestamp data_cadastro
    }

    ARTIGOS {
        int id PK
        string titulo
        text conteudo
        string resumo
        int escritor_id FK
        timestamp data_publicacao
        enum status
    }

    ESCRITORES ||--o{ ARTIGOS : "escreve"
```

**🧪 Uso no Service:**
```javascript
class ArtigoService {
    constructor(artigoRepository, escritorRepository) {
        this.artigoRepository = artigoRepository;
        this.escritorRepository = escritorRepository;
    }

    async listarArtigosPorEscritor(escritorId, page = 1) {
        // Validar se escritor existe
        const escritor = await this.escritorRepository.buscarPorId(escritorId);
        if (!escritor) {
            throw new EscritorNaoEncontradoError(escritorId);
        }

        // Buscar artigos com paginação
        const resultado = await this.artigoRepository.buscarPorEscritorIdComPaginacao(
            escritorId,
            page,
            10
        );

        return {
            escritor: {
                nome: escritor.nome,
                bio: escritor.bio
            },
            ...resultado
        };
    }
}
```

!!! tip "Dicas para Próximos Estudos" - Pratique Normalização de banco (1NF, 2NF, 3NF) - Configure Índices para melhorar performance de JOINs - Implemente Soft Delete em vez de DELETE físico - Use Query Builder (Knex.js) ou ORM (Prisma, TypeORM)

Navegação

← Exercício 07 | Próxima Solução →