Alura > Cursos de Data Science > Cursos de NoSQL > Conteúdos de NoSQL > Primeiras aulas do curso Datomic: geradores, schemas e índices

Datomic: geradores, schemas e índices

Leaf generators - Introdução

Bem-vindo a mais um curso de Datomic. Nesse curso a gente vai falar de queries mais complexas e mais lerdas, principalmente. A gente vai falar da geração de dados. Então a gente vai gerando diversos dados e ligando tudo que a gente tinha visto de Clojure, de geração de testes e de Datomic. Então a gente vai juntar tudo isso.

Não vamos estar criando testes para os bancos, mas a gente vai estar gerando dados para a gente entupir os bancos e criar queries mais complexas que são mais lerdas e ver como a gente pode otimizar isso, ver como funcionam os índices e utilizar esses índices.

E a gente vai criar também um framework que o nosso simples de geração de schema. Baseado no nosso modelo, a gente vai ser capaz de criar os nossos schemas do Datomic automaticamente.

Claro, isso é um guidelines, é um caminho que a gente pode seguir na nossa empresa. Você pode definir que você vai fazer tudo na mão, você pode definir que você vai utilizar o framework como a gente está criando aqui e, claro, você pode expandir isso e utilizar mais conceitos de composição, de mapas de função etc., que a gente foi vendo durante os cursos, para criar um framework mais complexo para as necessidades que você tem na sua empresa e diminuir o retrabalho.

Assim como em outras linguagens a gente usa ferramentas que usam SQL e de repente uma ferramenta que abstrai parte da SQL dos schemas e que gera esse código para a gente, é comum que a gente vá querer fazer isso também, é normal, é esperado, em Clojure e Datomic também. E é um pouco disso que a gente vai fazer aqui no final do curso, para que você possa escolher o caminho: vou querer criar minha própria ferramenta, minha própria camada que abstrai ou que gera esse tipo de código para mim.

Leaf generators - Geradores e leaf generators

Então vamos começar o nosso curso. Como de costume a gente vai pegar o projeto do curso anterior e vou copiar e abrir dentro do meu IntelliJ. Isso é, vou dar um file open project, ou só open, escolher o diretório onde está o meu projeto. No caso está em guilhermedatomic6. Projeto ecommerce. Eu abro o projeto aqui e a gente continua esse projeto.

Se você já fez o projeto do curso anterior, só continuar ele, se você não fez, mas acredita que consegue pegar o curso a partir daqui, sem problemas, só baixar o projeto de acordo com as atividades.

A gente tem aqui a aula1 até a aula5, que são as aulas do último curso. Eu vou apagar da aula1 até a aula4 e a aula5 na verdade eu vou renomear para aula1. Ele vai reclamar, eu mando corrigir para o nome adequado.

O que a gente vai fazer? As coisas de sempre: criar produtos, criar vendas e parar por aí. Parei por aqui.

Eu quero testar que isso está funcionando. Lembra de acompanhar? A gente vai lá, roda o nosso transactor, o transactor que é quem pode escrever no banco. Lembrando que a gente tem um transactor escrevendo no banco e pode ter outro transactor de backup para escrita também. É bem comum esse cenário. É o cenário comum aliás.

Comecei aqui o meu Datomic. Posso rodar o meu console se eu quisesse. Não é o meu caso. Eu não vou rodar o console. Eu vou rodar o project.clj só para ter certeza que eu estou com tudo ok. Mando rodar o REPL. E aí a gente vai carregar essa aula1 para ter certeza que estamos rodando, cadastrando produtos, cadastrando categorias, vendas, variações, que está tudo ok. Vamos dar uma olhada. Vou diminuir aqui o meu código. Diminui o meu código. Carrego meu arquivo.

Não encontrei o símbolo 1 do produto nesse contexto. Faltou um produto. O Guilherme esqueceu de escrever id um nesse arquivo. Aqui é simplesmente um nesse arquivo. Mas algum arquivo aqui que esteja com outra diferença? Não tenho certeza. Tenho que rodar e descobrir. Vamos recarregar. Recarreguei. Parece estar tudo ok.

Posso até rodar o REPL de novo para ter certeza. Enquanto eu carrego o REPL, eu já vou mover essas vendas. Essas três primeiras vendas que eu tenho aqui eu posso mover elas para dentro do nosso cria dados de exemplo. A gente pode deixar aqui dentro dos dados de exemplo. A gente criou várias coisas: categorias, exemplo dos produtos. Eu posso criar três vendas também. Essas três vendas que eu tenho aqui. Eu adiciono uma venda, uma segunda venda, uma terceira venda.

Claro, qual vai ser o id? Vai ser o id de algum produto. Eu posso escolher um produto, que pode ser o computador. Então eu vou adicionar três vendas aos meus computadores. Refere que eu não preciso desse def porque a gente não vai usar essas vendas. A gente não está usando isso daqui, a gente só vai executá-las, eu não estou interessado nos retornos. Então eu crio três vendas também. Criei três vendas de computadores.

O meu computador tem um id. Vai funcionar. Ok. Então aqui tem os produtos. Eu posso pegar o produto se eu quiser ou posso simplesmente imprimir os produtos se é o meu interesse. Eu poderia ter simplesmente um pprint para imprimir os vários produtos. E vou carregar então.

Vamos ver. db/venda. Faltou alguma coisa? Claro, porque nos exemplos a gente não está importando db/venda. Vamos lá. ecommerce, venda as db/venda. Recarrego. Está rodando. Está imprimindo os produtos.

Estou feliz com esses produtos? Não, não estou. Eu quero gerar muito mais produtos. Eu quero conectar aquilo que a gente já trabalhou em outros cursos, que são os geradores baseados em schemas. A gente já viu geradores baseados não em schemas, baseados em coisas menores. Depois a gente viu baseados em schemas, utilizando os schemas do Plumetic. E aí depois, agora, eu quero usar isso para gerar dados para o meu banco. Eu quero gerar um monte de dado no meu banco.

Eu queria ser capaz de gerar uma categoria. Por que uma categoria? Vamos lá no nosso modelo. Se a gente for no modelo, a gente tem a venda, a venda referencia um produto, o produto, a variação. A categoria é o mais simples. Então eu queria gerar uma categoria. Eu queria começar por aí.

Como a gente faz isso? A gente usa aquela biblioteca de schema generator da Plumetic. Eu quero usar essa biblioteca. Como eu uso ela? Preciso referenciar ela lá no meu project.clj. Vamos lá no project.clj. Vamos adicionar como dependência essa versão. A gente já tem o schema, mas agora eu preciso do Plumetic schema generators, que vai gerar os schemas, 0.1.3. Maravilha. Então eu tenho o schema generators aqui. Vai recarregar. Eu posso dar um import changes por aqui.

Lembrando que o REPL você tem que recarrega, você tem que dar um stop e um start.

Além disso, a gente vai utilizar aquelas funções de teste que geram samples para a gente. As funções que geravam samples para a gente era do test.check. Então, do Clojure, test.check era a biblioteca que a gente quer também colocar. Então o test.check eu também quero importar aqui para o meu projeto. org.clojure/test.check “0.10.0”. Eu vou colocar aqui. Salvei. Ele vai perceber a diferença. Posso dar um import ou lembrando que você pode clicar em Leiningen e dar um refresh aqui.

Vou esconder esse e vou dar um start no meu REPL agora, já com as bibliotecas carregadas. Com essas bibliotecas o que eu quero fazer? Quero começar a gerar alguns exemplos simples, usar um generator para usar alguns exemplos simples.

Os generators que utilizam modelos são essas da Plumetic, que conectam com o nosso schema da Plumetic. Baseado no nosso schema da Plumetic, eu posso chamar um g/sample e falar qual é o nome do meu modelo. Eu posso falar: “Olha, é o schema-generators. O schema-generators vai ter o exemplo para a gente. Então eu posso pegar um schema-generators. E aí você pode definir o seu ‘g’ de generators, generate, gen. O padrão aqui está parecendo ser ‘g’, mas, se você olhar a documentação do projeto, você vai achar generator. Como não tem um padrão ainda muito claro para os schema-generators da Plumetic, vamos manter o ‘g’, que é o que padrão na página inicial.

O meu ‘g’ aqui dele, a gente confere que o ‘g’ dele permite gerar samples sempre de acordo com o modelo. Então eu vou passar um schema, um g/sample, falar quantos que eu quero, por exemplo um, e eu quero uma categoria. Será que eu consigo criar uma categoria? Vou carregar esse arquivo inteiro, tentei carregar, ele tem que imprimir todos os produtos.

Todos os produtos, todos os produtos. Todos os produtos. Maravilha. Model que não funcionou. Por quê? Porque a gente não está nem importando model aqui. Faltou model. ecommerce as model. Vou recarregar. Agora deu um loaded. Parece que foi. Não imprimiu porque a gente não fez um pprint. Vou rodar o meu pprint. Olha aqui, uma categoria. Uma categoria com id e nome vazio.

Por que o nome está vazio? Gera 100 categorias. Lembra, depois os dados vão crescendo, crescendo. E o nosso sample acaba tendo nomes mais complexos. Então eu tenho aqui várias categorias.

Se eu quiser criar categorias para utilizar em teste generativos, para utilizar onde eu quiser, eu posso usar o g/sample. Tudo que a gente estava fazendo por enquanto parece compatível.

Tomar um cuidado. A categoria tem o id e o nome como obrigatórios. A variação tem mais. A variação tem o id, o nome e o preço. Então a gente tem que gerar mais coisas. Então a gente vai brincando com esses geradores. Então eu vou tentar gerar uma variação.

Vou gerar uma variação. Tento rodar. Deu caca. O que é a caca? Você tem que prover um leaf-generator para o schema. E aí uma caca enorme que você não consegue ler a mensagem de erro. Bem-vindo ao mundo.

O que está falando aqui? Está falando que não tem um gerador cadastrado para alguma coisa. Qual é a diferença entre a categoria e a variação? Isolar o problema. O problema é. A categoria tem o id e string, que claramente os generators do schema foram capazes de gerar. Aqui a gente tem o id/str e BigDecimal. BigDecimal ele não deve estar conseguindo gerar.

Se a gente procurar Clojure schema generator BigDecimal, você vai encontrar pessoas reclamando, falando: “Não existe generator para BigDecimal”. Por que não existe? Inclusive é a resposta. E a resposta faz sentido. A resposta é a seguinte. Não vamos criar geradores para tudo na biblioteca padrão. A biblioteca tem que ter o mínimo. E aí você quer gerador para todo tipo de coisa, de tudo que existe, tem que ter uma biblioteca separada. Então BigDecimal não possui um gerador para esse tipo.

O que acontece? O gerador, que gera valores para a gente, procura geradores de sequências, geradores de mapas. Em algum momento ele chega num gerador simples. Um gerador de mapa é um gerador de conjunto, gerador de sequência é um gerador de conjunto, gerador de tudo é gerador de conjunto. O gerador de uma string é um gerador simples, gera uma string. O gerador de um BigDecimal é para a gente gerar um BigDecimal. Quer dizer, é o finzinho de uma árvore de geração. Por isso que é um leaf-generator, é o gerador da folhinha, é bem ali na folhinha.

Não tem esse gerador. A gente tem que cadastrar um gerador. Se a gente der uma olhada na documentação, no g/sample, em outros ‘g’s, existem variações dessa função que recebem mais argumentos. A gente pode receber mais argumentos. Que argumentos que a gente pode passar? A gente pode passar geradores próprios nossos. A gente pode cadastrar o nosso próprio leaf-generator para BigDecimal.

Eu posso passar aqui os meus geradores. O que eu tenho que fazer? Eu tenho que definir meus geradores para um mapa vazio por enquanto. Mas esse meu mapa vai falar que, quando for para gerar um BigDecimal, eu gostaria que você usasse alguma coisa minha, algum gerador meu.

Aqui sim eu estou falando que eu gostaria de utilizar um generator meu. Eu vou gerar um BigDecimal generator. Esse vai ser o meu generator. Então eu vou ter aqui um def BigDecimal generator, que vai fazer o quê? Que na verdade é um fmap, é uma aplicação. Ele primeiro vai gerar um double... Porque o BigDecimal é tipo um double. Já que o BigDecimal é tipo um double, o meu BigDecimal generator podia ser o generator do double, que é um Clojure test.check.generators/double. Esse é o generator de doubles. Isso aqui é um gerador de doubles.

Eu estou falando que o meu gerador de BigDecimal é o gerador de doubles? Não. Ele não é um gerador de doubles. Ele é um fmap, ele aplica um double-para-bigdecimal. E aí a gente define essa função. Double-para-bigdecimal como uma função que recebe um valor e devolve um BigDecimal. É isso que a gente tem que fazer: devolver um BigDecimal aqui. Só isso.

O que a gente está fazendo? Repara, a gente está usando os generators do test.check que a gente importou um test.check. Isso aqui a gente só não fez um require aqui em cima. Vamos fazer um require direitinho. E aí tem a questão que eu falei. Aqui um vai fazer ‘gen’ e o outro vai ficar ‘g’. Fica não muito bom. E não fica mesmo. Como o padrão desse aqui é ‘gen’, eu estou deixando o padrão ‘gen’ e o outro eu estou deixando ‘g’. Não fica lindo, não fica maravilhoso, eu concordo com isso. Então eu vou tirar aqui, formatar bonitinho.

O que eu estou fazendo? Eu estou falando que é para gerar um sample de uma variação usando esses geradores se necessário. Se ele precisar ele vai procurar aqui um BigDecimal e vai achar gerador. O que esse gerador é mesmo? É um fmap, que a gente já viu, que usa esse gerador, o gen/double, e aplica essa função para transformar ele num BigDecimal.

Por exemplo, vamos fazer aqui que para transformar um double para BigDecimal basta chamar um construtor de BigDecimal do Java com o valor 15. Eu estou forçando todos os BigDecimals serem 15 só para a gente ver funcionar. Vou carregar o arquivo. Carreguei. Tenho várias categorias. E, olha, uma variação com o preço 15. Sem variações com o preço 15.

Aqui é só eu transformar um double para um BigDecimal. É o nosso próximo passo. A gente vai fazer daqui a pouquinho. E ver os problemas e desafios que a gente tem.

Leaf generators - Customizando nossos generators

Nosso próximo passo na geração aqui dos nossos dados é. Vamos deixar o número 15. A gente está gerando doubles aleatórios. Por favor transforme de verdade de double para BigDecimal. Então aqui eu vou passar um valor. Mas, se eu olhar o código-fonte Java da classe BigDecimal, você pode ir no navegador Java, Java PI... E aí lembrar que tem versão Java 7, mas não vai ter muita diferença. O construtor do BigDecimal recebe uma string. Tem versão que recebe long, tem versão que recebe double, tem versão que recebe string, tem versão que recebe um monte de coisa.

Então o que a gente quer fazer aqui? A gente quer passar esse valor para o nosso BigDecimal, testar e ver o que acontece. Vamos testar. Eu posso testar aqui a minha variação. Eu tenho que recarregar porque eu tenho que redefinir isso aqui. Eu só vou diminuir a quantidade de categorias e de variações. Recarrego o meu arquivo. E tenho vários preços interessantes, 1, -4,25, -7, 0,54, 0,5, menos tanto e por aí vai. Parece que está funcionando. Nas variações parece que está funcionando.

Lembra, você vai ter que fazer um pouco de interoperabilidade, seja com Java ou com Javascript se você usa Clojurescript, e vai ter que conhecer um pouco mais de Java e Javascript. Corre atrás. A gente tem os cursos, existem livros, existe muito material sobre essas áreas para a gente.

Aqui eu tenho essas 10 variações. Vamos voltar e aumentar para 100 variações. Vou rodar de novo. Deu caca. O que aconteceu? Ele falou que deu erro de sintaxe, infinito ou NaN. Compilando na linha 28. Quer dizer, nessa linha deu caca. Infinito ou not a number. Calma aí. Vou tentar 10 de novo. Funcionou. 10 de novo. 10 de novo. Opa. O problema não é 100, o problema é a aleatoriedade.

A aleatoriedade de um gerador de double significa o quê? Que ele vai gerar valores doubles possíveis. Quais são os valores doubles possíveis? Gera double, que são floating points de 64 bits, inclusive mais ou menos infinite e not a number. Inclusive isso. Ele gera isso. Ele fala que, se você quiser mais controle, você pode usar o double. O double que está aqui. O double*, você pode chamar, passando certos parâmetros, falando se você quer infinito, falando se você quer um NaN, falando se você quer o min, falando se você quer o max. Você pode passar tudo isso como parâmetro aqui para a gente.

Você pode falar o valor mínimo, valor máximo, se você quer infinito e se você quer NaN. Por padrão o infinito é true, o NaN é true. Então isso zoa, bagunça tudo aqui a gente.

Então, se você não conhece código Clojure e tem medo de enfrentar o código, o que a gente faria? Deu infinito ou not a number. Quer dizer que o valor que está seja gerado é infinito. Então eu teria que aqui de alguma maneira verificar o valor. Porque tem um isInfinite. Se ele for infinito, eu devolvo 99999M por exemplo ou não, regere. Repare que fica trabalhoso.

Se eu tivesse medo de enfrentar o código, de sair entrando no código, eu ia ter que ir por esse caminho. Eu ia ter que conhecer a PI de novo e ir entrando nesse caminho. Como eu não tenho medo, eu entra no gen/double. Por que o gen/double está usando infinite? Alguém deve ter passado por esse problema. O double* tem mais controle. Então vou chamar o double*.

Só que o double*, quando eu for chamar, eu vou querer chamar ele com alguns parâmetros. Isso é, o infinite é false. O padrão do NaN, ele não é o true direto, NaN é false. Eu gostaria que fosse assim. Vamos tentar novamente. Recarrego o arquivo. Gerou. Mas como é que eu sei se funcionou? Gera 100. Como é que eu sei se funcionou? Não sei, porque ele gerou vários números aqui para a gente. Não tem como saber se ele vai gerar o infinito ou não.

A gente poderia forçar aqui um valor inválido, recarregar e ver o que acontece. Rodou. Vou tentar de novo. Deu infinito. Deu rápido o infinito. Então as chaves que a gente está passando não estão sendo validadas. Está sendo ignorada aquela chave. Mas parece que se eu passar infinite?, vou rodando e vai gerando de acordo com o que eu quero. Parece que sim. Isso parece que eu estou passando adequadamente.

Repara que eu estou sempre usando o parece. Claro, eu sei que eu estou passando, mas o que eu quis dizer é isso. Como aqui a gente não teve uma validação dos parâmetros... São parâmetros, mas são passados através de um mapa e no mapa não tem uma validação do que foi passado extra, a mais. Um typo qualquer, um erro de digitação qualquer pode gerar um erro a longo prazo. A gente tem que tomar esse cuidado.

Aqui essa é a sacada. Aqui a gente está com gerador de double que só gera não infinito e não not a number. É muito mais controlado. E aí a gente transforma o double para BigDecimal. Esse é o nosso processo de geração.

Claro, eu poderia extrair esse gen/double aqui para um gerador de double finito. Então isso aqui é um defn gerador de double infinito, ele não suporta infinito. E também o not a number. Ele também não suporta not a number. Ele já é isso. Então eu posso rodar. Que nem a gente fez antes, roda de 100 em 100. Vou rodando e ele está gerando, gerando um monte. Vou deixar 10, que é o suficiente para mim.

Então eu tenho um double para BigDecimal, que é um gerador de BigDecimal baseado em double. Mas o gerador de double está aqui, o gerador de BigDecimal está aqui.

E aí entra um problema: qual a nomenclatura a ser utilizada? Repara que os geradores que estão aqui estão num namespace chamado generators. O que pode ser que eu vou querer ter? Eu vou querer ter aqui um namespace chamado generators. Dentro do meu namespace chamado generators é onde eu vou colocar esses meus geradores aqui.

Aqui você está usando o gen/double* e o gen/fmap. Estou usando eles dois sim. Como eu estou usando esses dois, eu vou dar require nesses dois gens. É um gen no final. Carreguei esse arquivo. Está carregado.

Por outro lado, quando eu quiser usar os meus geradores, eu vou ter que usar o meu ecommerce, vamos importar ecommerce.generators as... E aí eu preciso definir o meu nome, qual o nome que eu vou usar para esses meus generators. Eu vou chamar de generators, puro, que são os meus generators. Então aqui /meus-geradores.

Eu acho que eu não esqueci nada. Vou recarregar. Está funcionando. Só vou tomar mais um cuidado aqui nesse meu arquivo: os nomes. Eu vou querer um double para BigDecimal. Transformação. Eu estou transformando de double para BigDecimal. Gerador de double finito.

Só que repara, como eu citei, os generators é simplesmente double*. Então o meu vai se chamar simplesmente double-finito. Aqui vai chamar simplesmente bigdecimal. Não tem a palavra “generator” ou algo do gênero, é só double-finito. Double para o BigDecimal, double finito.

Os meus geradores são os geradores customizados, os leaf-generators, o que você achar que faz sentido. “Leaf” é folha. Aí você vê a tradução que você achar que faz sentido. Eu vou deixar aqui leaf-generator. Vou deixar em inglês porque é o termo que utilizado ali. Vou recarregar. Posso ir rodando que está tudo funcionando. Agora está mais redondinho.

Tenho os meus geradores isolados, com uma nomenclatura que segue um pouco mais os generators dessas bibliotecas e eu posso gerar samples até mesmo com generators customizados.

Sobre o curso Datomic: geradores, schemas e índices

O curso Datomic: geradores, schemas e índices possui 90 minutos de vídeos, em um total de 25 atividades. Gostou? Conheça nossos outros cursos de NoSQL em Data Science, ou leia nossos artigos de Data Science.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda NoSQL acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas