Primeiros passos utilizando Datomic

Cesar Augusto Alcancio de Souza
7 min readSep 12, 2021

O Datomic é um banco de dados transacional, distribuído e um pouco diferente dos bancos de dados relacionais e não relacionais tradicionais, por exemplo: MySQL e MongoDB.

A ideia desse artigo é introduzir alguns conceitos básicos do Datomic e um exemplo de como funciona as operações simples de escrita e leitura. O exemplo completo está disponível no meu Github.

Adicionalmente também apresenta um passo a passo para preparar o seu ambiente para testar os primeiros códigos usando Datomic.

O primeiro passo para entender melhor as diferenças entre Datomic e os outros banco de dados é entender como o Datomic armazena os dados.

Como o Datomic armazena os dados?

O Datomic armazena os dados como um grande conjunto de fatos, como se fosse uma única e enorme tabela no banco de dados. Esses fatos são chamados de datoms.

Os datoms são dados imutáveis e atômicos que representam as informações adicionadas e retraídas (removidas) em uma entidade na base de dados. Imutável porque uma vez inserida na base de dados não é possível modificar seu valor; e atômico porque não se pode adicionar parcialmente a informação de um datom.

Um datom é composto por: id da entidade, atributo, valor, id da transação e um indicador se o datom foi adicionado ou retraído.

Vamos analisar um exemplo, vamos supor que temos um banco de dados que armazena um conjunto de filmes, esse banco de dados se chama movies.

Nesse banco de dados queremos armazenar o título do filme, gênero, ano de lançamento. Também é interessante armazenar um identificador único para o filme e quando o registro foi criado.

Após registrar o filme Shutter Island na nossa base de dados, seus datoms podem ser visualizados na seguinte forma.

Datoms armazenados no Datomic

Um grande vetor, onde cada posição é um novo vetor que representa o datom com: id da entidade, atributo, valor, id da transação e um booleano para representar se o dato foi adicionado ou retraído.

Essa informação também pode ser visualizada em formato de tabela, que é mais familiar para quem trabalha com banco de dados relacionais.

Datoms em formato de tabela

Agora que sabemos como uma entidade e seus datoms são armazenados no Datomic vamos ver como definir um schema.

Definir o schema do banco de dados

O schema basicamente informa ao datomic a estrutura dos dados: quais serão os atributos armazenados e seus respectivos tipos de dado. Esse processo é parecido com a criação de tabelas e suas colunas em um banco de dados relacional. No nosso exemplo os atributos do filme são: movie/id como uuid, movie/title como string, movie/genre como string, movie/release-year como long e movie/created-at como instant.

Esse código está criando um cliente client para se comunicar com o Datomic dev-local que é uma biblioteca do Datomic para ser usada para testes. Os dados serão armazenados no file system indicado com o atributo :storage-dir .

Com o cliente é criado um base de dados chamada movies e posteriormente é gerada uma conexão com essa base de dados.

Por fim é realizado a transação que vai adicionar o schema na base de dados para que seja possível adicionar os datoms posteriormente. Os dados necessários para definir o schema são:

  • db/ident que representa o nome do atributo
  • db/unique (opcional) apenas caso o valor seja único para a entidade, a entidade é representada pelo namespace movie , por exemplo, movie/id
  • db/valueType que representa o tipo: string, uuid, long and etc
  • db/cardinality que representa se a relação da entidade para este valor é de um ou vários, para este nosso exemplo vamos utilizar sempre um
  • db/doc (opcional) para adicionar alguma documentação ao nosso atributo

Depois de definir e transacionar o schema podemos fazer algumas operações básicas para ler e registrar um filme.

Adicionar, consultar e retrair datoms

Para adicionar os datoms que representam o filme vamos importar o namespace datomic.client.api e utilizar a função transact enviando a conexão com o datomic e um mapa com os dados da transação.

Os datoms são enviados em um vector:

  • A primeira posição representa a operação, nesse caso :db/add para adicionar o datom
  • A segunda posição é o id temporário da entidade. Nesse caso colocamos todos iguais para que o datomic saiba que todos esses atributos fazem referencia a apenas uma entidade, ou seja, o mesmo filme
  • A terceira posição é o atributo, por exemplo, :movie/title
  • A quarta posição é o valor do atributo

Após inserir os datom que representam um filme podemos executar uma consulta no Datomic para trazer todos os datoms que representam os filmes.

Adicionando e consultando datom Harry Potter

Para a consulta, o Datomic vai percorrer todos os datoms adicionados e vai aplicar as cláusulas where para cada datom, no nosso caso são cinco cláusulas where.

Vamos entender o primeiro vetor [?e :movie/id ?id]. Essa clausula indica que o Datomic vai trazer todos os datoms onde a primeira posição seja qualquer coisa, a segunda posição seja :movie/id e a terceira posição seja qualquer coisa. Os valores que começam com ? indicam que o Datomic vai trazer qualquer valor e atribuir este valor ao símbolo para que ele seja usado posteriormente, tanto no :find quanto nas seguintes clausulas :where.

Por exemplo, após inserir o filme Harry Potter, os datoms armazenados no Datomic serão:

Esses datoms são percorridos com a clausula [?e :movie/id ?id], o único datom que atende a essa clausula é o primeiro, consequentemente o valor 92358976733262 será atribuído ao ?e e o valor #uuid”3dea1d1d-9fc7–4ba8-a6fd-f5e1284e3084" será atribuído ao ?id.

O ?e vai ser utilizado no próximo where [?e :movie/title ?title] para que seja consumido o título da mesma entidade do movie/id e assim por diante para as próximas clausulas where. O ?id vai ser utilizado na clausula :find, que indica os campos que serão retornados pela consulta.

A clausula :key permite que seja especificado qual é o nome de cada campo que será retornado, para que os dados possam ser retornados em um formato de mapa em vez do formato tradicional dos datoms, nesse o valor retornado será.

Finalmente podemos retrair os datoms para “eliminar” o filme. Lembrando que o Datomic é imutável portanto o filme sempre estará armazenado, entretanto os datoms estarão indicados como false.

Retrair os datoms é muito parecido com adicionar os datoms, a diferença está no :db/retract em vez de :db/add e no lugar do id temporário da entidade temos que passar realmente o id da entidade. Como não temos o id da entidade mas temos o identificador único do filme, nesse caso podemos passar o vetor [:movie/id id] para que o Datomic internamente busque o id da entidade com base no id do filme e retraia os datoms.

Depois que os datoms são retraídos e executamos a consulta, os datoms não serão retornados pois a consulta retorna apenas os datoms que estão com valores true. Isso não significa que os dados foram removidos do file system, existem outras consultas mais avançadas no Datomic que possibilitam que dados retraídos possam ser consultados, ou até mesmo fazer consulta em outro ponto específico do tempo.

E como podemos atualizar um filme? Nesse caso o que fazemos é adicionar os novos datoms e automaticamente o Datomic vai identificar se a entidade que estamos inserindo é um id já existente (isso de acordo com o :movie/id que quando criamos o schema colocamos como único), e para este caso vai retrair o datom antigo e adicionar o datom novo, mantendo o histórico dos dados.

Conclusão

O Datomic é uma base de dados um pouco diferente das base de dados tradicionais, sua principal vantagem é manter todo o histórico da base de dados já que os dados uma vez inseridos nunca são removidos.

Acredito que conhecer como é exatamente um datom e como esses datoms são armazenados no Datomic é a parte mais importante para que seja possível entender as queries e como adicionar e retrair datoms.

Esse artigo foi apenas uma breve introdução ao Datomic, existe muito mais features que podem ser encontradas na documentação oficial.

Preparando o ambiente

Para preparar o ambiente é preciso instalar as seguintes ferramentas:

Plugin do Cursive no IntelliJ

O Datomic possui uma versão lightweight chamada dev-local que é muito boa para continuous integration e também para testes locais. Nesse nosso pequeno exemplo vamos utilizar esta versão. Você vai precisar preencher este formulário com o teu email para receber duas instruções por e-mail:

  • Um zip com um executável chamado install que vai instalar a dependência no seu maven local
  • Um link para configurar o seu maven settings xml

Você só precisa fazer um dos dois, no meu caso eu fiz o primeiro que considerei mais fácil, apenas instalei a dependência no meu maven local.

Formulário para receber o dev-tools
E-mail com o dev-tools

Depois de clicar no link para baixar o dev-tools, basta executar o arquivo “install” para poder utilizar o dev-local como dependência do seu projeto.

Install maven

Criando o projeto

Utilizamos o leiningen para criar o projeto, podemos utilizar ele por linha de comando: $ lein new app project-name

Criando o primeiro projeto Leiningen

Ou podemos utilizar o IntelliJ em “File” -> “New Project” e selecionar “Clojure” -> “Leiningen”.

Adicionando dependência

Uma vez que o projeto foi criado, basta adicionar a dependência dev-local ao project.clj. No meu caso foi adicionado como [com.datomic/dev-local “0.9.235”].

Adicionado a dependência do dev-local

--

--

Cesar Augusto Alcancio de Souza

Sofware Engineer Lead, focused on development and maintenance of products