# Aula 9
### Docker - Na prática
- Mostrar o código de dev/docker;
Em `server.js`, temos nossa estrutura normal de server feito com express.
Em `Dockerfile`, temos a estrutura da nossa imagem e os passos que o Docker irá realizar até montar ela;
Uma vez explicado, a gente pode montar nossa imagem com:
```bash=
docker build -t node-web-app .
```
Uma vez montada, podemos rodar nossa imagem com:
```bash=
docker run -p 8081:8080 -d node-web-app
```
Podemos confirmar que nossa aplicação está rodando de algumas maneiras:
Verificando a extensão no VSCode;
Verificando no terminal, com `docker container ls`;
Fazendo uma requisição para o endpoint que escolhemos;
### E a seguir?
Não faremos isso pras nossas aplicações nesse momento, em geral, vocês vão preferir sempre utilizar docker pra fazer suas aplicações e também para fazer deploy delas;
O que a gente aqui no entanto é fazer a instalação do nosso banco de dados dentro do docker pra gente não se preocupar com várias problemáticas ao longo do caminho.
Nós vamos falar muito ainda hoje sobre PostgreSQL e SQL, mas enquanto isso, vamos instalar ele.
### Instalando Postgres utilizando SQL;
O Docker possui um site bem parecido com o Github mas que para imagens de Docker, chamado DockerHub.
Lá vamos encontrar documentação e a nossa imagem do postgres que vamos utilizar.
`https://hub.docker.com/_/postgres`
Para iniciar uma instância vamos utilizar o código que eles falaram:
`docker run --name some-postgres -e POSTGRES_PASSWORD=mypw -d postgres`
Esse código vai além de rodar o postgres como também baixar a imagem caso você não tenha ela localmente;
Um equivalente pra isso seria chamar `docker pull postgres` antes do run e teriamos o mesmo efeito;
Poderiamos verificar o contexto do container de várias maneiras:
1. Verificando a extensão do VSCode;
2. Verificando via `docker container ls`;
3. Entrando no container usando `docker exec -it pg psql -U postgres`;
Agora vamos para o Heidi fazer nossa conexão;
Verificamos um erro de conexão. O que acontece é que não expomos nenhuma porta ao iniciar nosso container;
Portanto, vamos fazer isso:
`docker run -p 5432:5432 --name pg -e POSTGRES_PASSWORD=mypw -d postgres`;
Podemos ver em `docker container logs pg` que está rodando;
Podemos voltar ao Heidi agora;
## PostgreSQL
Até o momento a gente guardou as informações dos nossos serviços em memória e dessa maneira de forma volátil, uma vez que nossa aplicações reiniciasse por qualquer razão, nosso dados eram perdidos. Isso não é o ideal e por tanto, vamos começar a aprender como podemos guardar nossos dados de maneira persistente.
Nota: É importante dizer que apesar para os nossos casos é interessante guardar as informações de maneira persistente, existem casos e modelos de negócio que são satisfeitos pela utilização de guardar informações em memória, um caso de uso comum é cache, logs, e estruturas de dados que podem ser transferidos ou mantidos primeiramente em memória e posteriormente em banco persistente.
Quando entramos em persistência e banco de dados, nós entramos num grande mundo, que por si só é uma profissão, ou melhor, várias profissões. Vamos desde criação, normalização, performance, índices, clusters, horizontalização, verticalização, relacionais, não relacionais, propriedades ACID, atomicidade, consistência, isolamento, durabilidade, Big Data, várias buzzwords.
Vamos ver uma pontinha do iceberg hoje e ao longo da carreira de você de vocês, vocês vão ver que o buraco é muuuito mais embaixo.
Hoje falaremos de SQL e um pouco de banco de dados relacionais.
Primeiro, vamos para banco de dados relacionais:
### Bancos Relacionais e Não-Relacionais
Existem muitas listas mostrando alguns apontamentos se você deve utilizar um banco relacional ou não-relacional. Em alguns desses pontos, cria-se inclusive um favoritismo, boas discussões sobre o que deve-se ou não utilizar.
Não vamos entrar nessa ciranda. Sugerimos que você aprenda exemplos dos dois mundos, talvez aprender além do postgreSQL, que falaremos exclusivamente até o final do curso, também aprendam sobre MongoDB ou talvez RethinkDB.
De uma maneira simples, um banco de dados relacional representam seus dados de forma tabular.
**Imagem 1.**

### Chaves primárias (primary keys) e chaves estrangeiras (foreign keys)
Conforme a imagem (1), nós vamos que temos algumas entidades como `users`, `movies`, `tags`, `ratings`.
As entidades `users` e `movies` não referenciam nenhuma outra entidade. No entanto, as entidades `tags` e `ratings` referenciam `users` e `movies`.
As chaves identificadas apenas como `id` são chaves primárias, isso significa que esse identificador refere-se a um identificador único dentro da tabela. Então, por exemplo: teriamos um id `102030` para um registro na tabela `users`, e esse id é único por toda a tabela. Em caso de tentarmos adicionar um novo registro com este mesmo id receberiamos um erro possívelmente de `Violation of PRIMARY KEY constraint`.
Enquanto nas tabelas `tags` e `ratings`, somos apresentados a outra forma de chave, os campos `user_id` e `movie_id` são consideradas chaves estrangeiras (`foreign keys`), essas chaves relacionam outras tabelas e suas chaves primárias com essa referência.
Pode-se ler, portanto, que `ratings` possui um valor `rating` e além disso, existe *alguma forma* de relação com `user_id` e `movie_id`. O banco de dados é agnóstico em como essa relação vai funcionar, isso vai depender de como será tratado a nível de aplicação. Isso é um trato importante de como os dados são armazenados: é importante definir uma relação entre os dados, mas não definir como quando se trata de banco de dados.
Ao longo das aulas falaremos mais suas particularidades.
### SQL, Structural Query Language
A linguagem SQL é uma só. Ela, no entanto, é divida em diversas 'categorias ou tipos',
#### DDL, Data Definition Language;
Como o nome sugere, o comando que cai sob essa categoria trabalha com definir uma tabela ou banco.
- **CREATE** - Cria um novo banco ou tabela, ou conteúdos dentro disso;
- **ALTER** -Modifica a estrutura de uma tabela ou banco;
- **DROP** - Deleta uma tabela ou banco;
- **TRUNCATE** - Remove todos os registros e os espaços alocáveis de uma tabela;
- **RENAME** - Renomeia um banco ou uma tabela;
Exemplos:
```sql
CREATE TABLE table_name (
field1 type ...options,
field2 type ...options
);
```
```sql
DROP TABLE table_name;
```
#### DML, Data Manipulation Language;
Quando você atualiza o próprio dado, ou realiza operações sob o dado, você está usando DML.
DML permite que você trabalhe com o dado sem modificar o container (tabela). Uma cópia do dado é criada e as operações são executadas em relação a essa cópia.
- **INSERT** - Adiciona novos registros (linhas) em uma tabela;
- **UPDATE** - Atualiza dados existentes como linhas completas ou campos dessa linha;
- **DELETE** - Deleta registros de uma tabela;
- **MERGE** - Também chamado de UPSERT(como UPDATE/INSERT), `MERGE` é usado para inserir novos registros ou atualizar aqueles já existente baseado em condições;
#### DCL, Data Control Language;
Em termos SQL, ou em termos de dados, essa categoria é sobre autorização.
- **GRANT** - Altera permissões de acesso para um usuário para os dados dentro de um banco de dados;
- **REVOKE** - Remove os privilégios que um usuário tem em relação um dado específico.
#### TCL, Transaction Control Language;
Qualquer ação que faz uma alteração ao dado é chamado de transação. Quando vocẽ efetua uma operação de manipulação de dados, a manipulação ocorre em um local temporário e não na própria base/tabela;
A fim de escrever ou remover algo do banco, você precisa usar um comando para perguntar ao banco para 'atualizar-se' com o novo conteúdo;
- **COMMIT** - Salva as alterações permanentemente no banco de dados;
- **ROLLBACK** - Restora o banco de dados ao formato original antes do último commit;
- **SAVEPOINT** - Cria um ponto de 'referência' para ser usado como restaurar as novas alterações;
- **SET TRANSACTION** - Pode configura a transação para ser de apenas leitura, por exemplo.
#### DQL, Data Query Language;
É usada para obter dados com o comando `SELECT`.
Nota: alguns autores colocam `SELECT` como estando na *DML*.
* **SELECT** - retorna dados estáticos ou retorna dados de uma tabela baseado em certos parâmetros.
```sql
SELECT nome FROM users;
```
### Datatypes
Dùvida: Como armazenariamos informações de usuário como:
- Data de Nascimento: date;
- Nome de pessoas: varchar;
- Textos variáveis: text;
- Horários: time;
- Valores monetários: longint;
- Número de CPF: varchar(11);
- Número de Telefone: var(20);
E os tipos de dados?
O tipo de dados suportado vai depender do RBDMS que você está utilizando. O postgreSQL por exemplo, utilizam alguns desses tipos:
**Imagem 2.** Númericos;

**Imagem 3.** Booleanos e Geométricos;

**Imagem 4.** Binários e Datas;

**Imagem 5.** Seriais, monetários e Chartypes;

Fonte das Imagens: https://tableplus.com/blog/2018/06/postgresql-data-types.html
Poderiamos por exemplo aplicar alguns filtros.
```sql
WHERE short_url like 'some%';
WHERE short_url like '_ome%';
WHERE short_url like 'som_';
```
`LIKE` pode ser substituido por `~~`;
Existe o contexto de Insentitive Like que pode ser usado como `ILIKE` ou `~~*`;

Podemos limitar a quantidade de dados utilizando `LIMIT = n` e `OFFSET = n`
Ordenar por `ORDER BY campo ASC/DESC`;
Agora vamos por partes, lembra da nossa estrutura do encurtador de urls?
```typescript
{
"short-url": string
"base-url": string
}
```
### Encurtador e Banco de Dados;
Como poderia ficar esquematizado em SQL?
```sql
CREATE TABLE shortened (
short_url TEXT NOT NULL,
base_url TEXT NOT NULL
);
```
Mas o que acontece se eu adicionar um mesmo link?
```sql
CREATE TABLE shortened (
short_url TEXT NOT NULL UNIQUE,
base_url TEXT NOT NULL
);
```
Agora vamos tentar adicionar novamente. Provavelmente receberemos um erro.
Para o nosso encurtador de urls, vamos precisar de três métodos para o model;
- insert;
- getItemByBaseURL;
- getItemByShortCode;
Vamos montá-los:
```typescript
const getByShortCode = async (short_code) => {
try {
const { rows } = await Pool.query('SELECT * FROM shortener WHERE short_code=$1 LIMIT 1', [short_code]);
console.log(response.rows);
return rows;
} catch (err) {
console.error(err);
return {
error: 503,
message: 'Internal Error',
};
}
}
const getItemByBaseURL = async (base_url) => {
try {
const { rows } = await Pool.query('SELECT * FROM shortener WHERE base_url=$1 LIMIT 1', [base_url]);
return rows;
} catch (err) {
console.error(err);
return {
error: 503,
message: 'Internal Error',
};
}
}
const insertOne = async (short_code, base_url) => {
try {
const { rows } = await Pool.query('INSERT INTO shortener (short_code, base_url) VALUES ($1, $2) RETURNING *', [short_code, base_url]);
return rows;
} catch (err) {
console.error(err);
return {
error: 503,
message: 'Internal Error',
};
}
}
```
Não esqueça de expotar esses métodos;
```typescript
module.exports = { insertOne, getItemByBaseURL, getByShortCode };
```
### CRUD e Banco de Dados
E se fossemos refazer nosso CRUD utilizando banco de dados?
Primeiro passo seria novamente adicionar `pg` como dependência usando `npm add pg`, e então criar um arquivo de referência da Pool;
```typescript
// config/db.js;
module.exports = {
user: 'postgres',
host: 'localhost',
database: 'postgres',
password: 'mypw',
port: 5432,
};
```
```typescript
// db.js
import dbConfig = require("./config.db");
const pool = new Pool(dbConfig);
module.exports = pool;
```
Após isso, a gente precisa começar os métodos dos models.
Vamos precisar de alguns métodos,
- getOne
- getAll
- insertOne
- deleteOne
- updateOne
Vamos começar pelos mais simples:
### Get e GetAll
```typescript
// src/user/model.js
const Pool = require("../db/db");
const getOne = async (id) => {
try {
const { rows } = await Pool.query(
'SELECT * FROM users WHERE id = $1', [id],
);
return rows;
} catch (err) {
console.error(err);
return {
error: '503',
message: 'Internal Error';
}
}
}
```
Vamos alterar nosso controller e testar:
```typescript
const response = await User.getOne(id);
if (response.length) {
const { id, name, username, email } = response[0];
return res.json({ id, name, username, email });
}
res.status(404).json({
error: 404,
message: "Not Found",
});
```
Agora é a vez do `GET ALL`:
```typescript
try {
const { rows } = await Pool.query(
'SELECT * FROM users OFFSET $1 LIMIT $2;', [(page - 1) * limit, limit],
);
console.log(rows);
return rows;
} catch (err) {
console.error(err);
return {
error: '503',
message: 'Internal Error'
};
}
```
Vamos alterar nosso controller e testar também:
```typescript=
const users = await User.getAll();
if (response.error) {
return res.json({
error: 503,
message: "Internal Error",
});
}
return res.json(response.map(users.map(({ password_hash, ...rest}) => rest)));
```
### Insert
Agora o nosso `INSERT`:
```typescript
const { v4: uuid } = require("uuid");
const insertOne = async (user) => {
try {
const { rows } = await Pool.query(
'INSERT INTO users (id, name, username, email, password_hash) VALUES ($1, $2, $3, $4, $5) RETURNING *', Object.values(user)
);
return rows;
} catch (err) {
console.error(err);
return {
error: '503',
message: 'Internal Error';
}
}
};
```
O SQL permite você adicionar campos por padrão em caso de não serem definidos no insert, a gente poderia fazer algo mais legal ao invés de adicionar mais uma dependência do projeto;
A fim de praticidade, vamos dropar a tabela `users`;
```sql
DROP TABLE users;
```
Agora vamos criar uma extensão do postgres para criar automaticamente um uuid;
```sql
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
```
E agora vamos alterar nossa estrutura da tabela `users` e utilizar o uuid;
```sql
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(50) NOT NULL,
username VARCHAR(20) NOT NULL,
email VARCHAR(40) NOT NULL UNIQUE,
password_hash VARCHAR(60) NOT NULL
);
```
E no nosso código, vamos alterá-lo para que possamos apenas passar as informações que de fato precisamos:
```typescript
// src/user/model.js
const insertOne = async (user) => {
try {
console.log(Pool);
const { rows } = await Pool.query(
'INSERT INTO users (name, username, email, password_hash) VALUES ($1, $2, $3, $4) RETURNING *', Object.values(user)
);
return rows;
} catch (err) {
console.error(err);
return {
error: 503,
message: 'Internal Error'
};
}
};
```
Precisamos alterar nosso controller tambem:
```typescript
// src/user/controller.js
const User = require("./model");
const user = {
name,
username,
email,
};
const response = await User.insertOne({ ...user, password_hash });
if (!response.length || response.error) {
return res.json({
error: 503,
message: 'Internal Error',
});
}
res.status(201).json(user);
```
Vamos testar?
E também podemos remover o uuid como dependência;
### Delete
Podemos criar agora nosso método de `DELETE`:
```typescript
// src/user/model.js
const deleteOne = async (id) => {
try {
const response = await Pool.query(
'DELETE FROM users WHERE id = $1', [id]
);
console.log(response);
} catch (err) {
console.error(err);
return {
error: 503,
message: 'Internal Error'
};
}
}
```
Agora vamos alterar nosso controller:
```typescript
// src/user/controller.js
const response = await User.deleteOne(id);
if (response.error) {
return res.json(error);
}
return res.send();
```
### Update
Agora vamos para o nosso `UPDATE`:
```typescript
try {
const { rows } = await Pool.query(
'UPDATE users SET name = $1, username = $2, email = $3, password_hash = $4 WHERE id = $5 RETURNING *', Object.values(user)
);
console.log(rows);
return rows;
} catch (err) {
console.error(err);
return {
error: 503,
message: 'Internal Error',
}
}
```
Agora vamos alterar nosso controller:
```typescript
const response = await User.updateOne({ name, username, email, password_hash, id });
if (response.length) {
return res.json({
id,
name,
username,
email
});
}
return res.json({
error: 404,
message: 'Not Found',
});
```
Vamos testar;