Banco de Dados

30 out, 2017

Firebase Database não é bagunça!

Publicidade

Sim, temos regras e validações e você irá se surpreender.

Psiu! Não conte a ninguém, mas até “foreign keys” tem. Maluco, né?

A primeira impressão que se tem de uma base de dados não relacional como o Firebase, é de que ela simplesmente aceita tudo. E na realidade, aceita. Mas podemos limitar colocando regras, e é isso que iremos ver nesse artigo.

A principal vantagem que eu vejo numa base NoSQL, é não ter schema. Passei grande parte da minha vida escrevendo tabelas, tipando cada coluna, definindo PKs e FKs.

Vamos lembrar de como era. Apenas para termos uma comparação, criaremos uma tabela de estado e uma tabela de pessoa, com suas devidas PKs e FKs.

 CREATE TABLE Estado (
       EstadoId int NOT NULL PRIMARY KEY,
       Sigla varchar(2) NOT NULL
    );

    CREATE TABLE Pessoa (
       PessoaId int NOT NULL PRIMARY KEY,
       Nome varchar(50) NOT NULL,
       SobreNome varchar(50),
       email  varchar(30) NOT NULL,
       Idade int,
       Cidade varchar(50),
       EstadoId int
    );

    ALTER TABLE Pessoa
    ADD FOREIGN KEY (EstadoId) REFERENCES Estado(EstadoId);

Ficaram saudosistas, né? Vamos fazer algo parecido com json. Bom, como não temos como a definição, iremos colocar os dados também.

"estados" : {
"AC" : true,
"AL" : true,
"AM" : true,
"AP" : true,
"BA" : true,
"CE" : true,
"DF" : true,
"ES" : true,
"GO" : true,
"MA" : true,
"MG" : true,
"MS" : true,
"MT" : true,
"PA" : true,
"PB" : true,
"PE" : true,
"PI" : true,
"PR" : true,
"RJ" : true,
"RN" : true,
"RO" : true,
"RR" : true,
"RS" : true,
"SC" : true,
"SE" : true,
"SP" : true,
"TO" : true
}


"pessoas": [
    {
      "nome": "Evelyn",
      "sobrenome": "Mendes",
      "email": "evelyn@transnerd.com",
      "idade": 35,
      "cidade": "Porto Alegre",
      "estado": "RS"
     }
  ]

Diferente, né? Mas até ai são apenas dois json que não tem relação nenhuma com o outro, e que no caso, aceitam qualquer valor em suas chaves. Um exemplo: Vamos imaginar que alguém, ao invés de colocar 35, um número inteiro, coloque qualquer coisa no valor dessa chave “idade”. “Trinta e cinco” eu acho que não ficaria legal e prejudicaria muito uma pesquisa. Para resolver isso, iremos usar as regras do banco de dados do Firebase.

Ao entrar nas regras, você verá algo bem simples definido.

{
  "rules": {
    ".read": true,
    ".write": true
  }
}

Onde diz que toda a base de dados pode ser lida e gravada livremente por qualquer usuário. Mas isso não é nada legal. Quem sabe deixemos apenas que leiam, mas não que escrevam? E como fazemos isso? O Firebase tem server variables. Uma delas é a auth, que traz o usuário logado e com essa variável podemos fazer uma simples modificação.

{
  "rules": {
    ".read": true,
    ".write": "auth != null"
  }
}

Pronto, agora somente usuários logados podem escrever na base de dados. Agora vamos voltar à situação da idade e validar para apenas receber inteiros. Regras novamente.

{
  "rules": {
    ".read": true,
    ".write": "auth !=null",

    "usuarios": {
      "$uid": {
        ".read": true,
        ".write": "auth.uid == $uid",
        ".validate": " newData.child('idade').isNumber()"
      }
    }
  }
}

Perceba a condição:

".validate": " newData.child('idade').isNumber()"

Ela validará se somente números serão imputados para a chave idade.

Outra coisa que você deve ter notado, é o $uid. Ele é um CURINGA (WILD CARD). Nesse caso ele representa o ID do usuário que gravou o registro. Por isso que em .write, exite “auth.uid == $uid”. Somente o dono do registro poderá escrever nele. O auth, como eu já disse, é um server variable. Existem 6 server variables, vamos a elas.

Server variables do Firebase

  • auth: É o usuário autenticado
  • data: São os dados existentes no caminho, por exemplo, pessoa/idade
  • newData: São os dados postados no caminho. Esse variável só está disponível em .write e .validate pois somente nesses momentos que novos dados serão postados
  • now: É uma marcação de data e hora de tempo real do servidor
  • root: Levará de volta a raiz do banco de dados
  • $ Curinga, como usamos em $uid para pegar o ID do usuário do registro

Voltando à situação de validações, podemos definir também que os campos textos somente receberão strings e terão um tamanho determinado. Vamos ver como fica no campo nome, que além de tudo não pode ser nulo?

{
    "rules": {
        ".read": true,
        ".write": "auth !=null",

        "usuarios": {
        "$uid": {
            ".read": true,
            ".write": "auth.uid == $uid",
            ".validate": "newData.child('idade').isNumber() &&
                            newData.child('nome').exists() &&
                            newData.child('nome').isString() && 
                            newData.child('nome').val().length < 51 "
            }
        }
    }
}

Podemos repetir isso para os demais campos.

{
    "rules": {
        ".read": true,
        ".write": "auth !=null",

        "usuarios": {
        "$uid": {
            ".read": true,
            ".write": "auth.uid == $uid",
            ".validate": "newData.child('idade').isNumber() &&
                            newData.child('nome').exists() &&
                            newData.child('nome').isString() && 
                            newData.child('nome').val().length < 51 &&
                            newData.child('sobrenome').isString() && 
                            newData.child('sobrenome').val().length < 51 &&
                            newData.child('cidade').isString() && 
                            newData.child('cidade').val().length < 51"                     
            }
        }
    }
}

A partir daqui a brincadeira começa a ficar mais interessante. Vocês viram que tem um campo de e-mail, não é?

Quem sabe a gente não valida, já que é um endereço de e-mail válido. Para isso, usaremos as famigeradas expressões regulares.

{
    "rules": {
        ".read": true,
        ".write": "auth !=null",

        "usuarios": {
        "$uid": {
            ".read": true,
            ".write": "auth.uid == $uid",
            ".validate": "newData.child('idade').isNumber() &&
                            newData.child('nome').exists() &&
                            newData.child('nome').isString() && 
                            newData.child('nome').val().length < 51 &&
                            newData.child('sobrenome').isString() && 
                            newData.child('sobrenome').val().length < 51 &&
                            newData.child('cidade').isString() && 
                            newData.child('cidade').val().length < 51 &&
                            newData.child('email').isString() && 
                            newData.child('email').val().matches(/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$/i)"
             }
          }
       }
    }

Viram que lindo? Pequeno ponto de observação: regExp para e-mails nem sempre conseguem suprir todas as necessidades, portanto, cautela ao usar.

E o estado? Até agora apenas temos aquela lista json separada, mas sei que ele também deve entrar em nosso objeto pessoa. Mas só se existir na tabela estados. Você quer dizer uma FK? Não. Quero apenas validar se existe o valor. Coisa simples, nada de mais. Mas como fazemos isso? Iremos acessar a tabela estados usando a server variable root, e com ela, verificar se o dado imputado está contido nela.

Antes de colocarmos as regras, é necessário dizer que o estado pode ser uma tabela que não deverá ser modificada de fora da base de dados, pois seus dados quase nunca são alterados. O que se pode fazer, é deixar .read e .write como false. Assim, somente entrando no console>realtimedatabase você poder alterar os valores, adicionar e excluir.

{
    "rules": {
        ".read": true,
        ".write": "auth !=null",
        "estados" :{
            ".read": false,
           ".write": false,
          },
        "usuarios": {
        "$uid": {
            ".read": true,
            ".write": "auth.uid == $uid",
            ".validate": "newData.child('idade').isNumber() &&
                            newData.child('nome').exists() &&
                            newData.child('nome').isString() && 
                            newData.child('nome').val().length < 51 &&
                            newData.child('sobrenome').isString() && 
                            newData.child('sobrenome').val().length < 51 &&
                            newData.child('cidade').isString() && 
                            newData.child('cidade').val().length < 51 &&
                            newData.child('email').isString() && 
                            newData.child('email').val().matches(/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,4}$/i) && 
                            root.child('estados').child(newData.child('estado').val()).exists()"
             }
          }
       }
    }

Vejam como é fácil entender o que ele faz, ele vai até a tabela estados.

root.child('estados')

Referencia os filhos passando o valor que veio objeto newData.

.child(newData.child('estado').val())

E por fim, pergunta se esse valor existe na coleção.

.exists()

Não é simples?

root.child('estados').child(newData.child('estado').val()).exists()

Para entender melhor:

root.child('estados').child('RS').exists()

Existe o estado!

"estados" : {
   "RS" : true,
}

E como vou saber se isso tudo está certo? Sim, eu sei que é um pouco complexo e subjetivo em alguns momentos. É por isso que se usa o simulador das regras.

Exemplo de gravação com tudo ok

Exemplo de gravação com idade errada, como string

Também podemos testar a leitura dos dados sem autenticar

O Firebase Database é uma ferramenta muito poderosa. Esses exemplo do artigo chegam a ser simples para tanta coisa que ele pode oferecer. Aos poucos vamos falando mais sobre tudo que o baas (backend as a service) Firebase pode nos oferecer.

Espero muito que tenham gostado! Obrigada.

Atualização Firebase: Firestore

Lançaram a nova base de dados NoSQL do Firebase, o Firestore. Então resolvi dar uma pequena amostra de como funcionam as validações dentro dele. Um pouco diferente, mas o conceito é o mesmo. O legal: tem funções nas validações. O não legal: não é mais json e não tem simulador. Pelo menos até agora, dentro do console web, não tinha.