Realm é a melhor solução de banco de dados Android

Publicados: 2022-03-11

Desde que o Android foi criado, nós, desenvolvedores de aplicativos, usamos o SQLite para armazenar nossos dados locais. Às vezes diretamente com instruções SQL, às vezes usando um Mapeador Relacional de Objeto (ORM) como uma camada de abstração, mas de qualquer forma, estamos usando SQLite no final do dia.

Apesar de todas as vantagens do SQLite, houve momentos em que desejamos ter alternativas para um modelo relacional: algo que pudesse nos poupar de ter que adicionar código clichê para converter valores de e para o banco de dados ou nos permitir pular a configuração de mapeamentos entre classes e tabelas, campos e colunas, chaves estrangeiras, etc.

Em outras palavras, um banco de dados com estruturas de dados mais semelhantes às que realmente usamos no nível da aplicação. Melhor ainda, se pudesse ser eficiente em memória por design, permitindo melhores experiências em dispositivos com recursos limitados, isso seria incrível.

Esses são, de fato, alguns dos benefícios out-of-the-box que obtemos com o Realm, uma plataforma de banco de dados com uma arquitetura distinta, que surgiu como uma nova alternativa ao SQLite.

Este artigo apresenta algumas das principais razões pelas quais o Realm chamou tanta atenção e por que você pode querer considerar experimentá-lo. Ele discute algumas das principais vantagens que o Realm oferece aos desenvolvedores Android em relação ao SQLite.

Como o Realm está disponível em várias plataformas, parte do que será abordado neste artigo também tem relevância para outras plataformas móveis, como iOS, Xamarin e React Native.

SQLite: funciona, mas não é o que você precisa na maioria das vezes

A maioria dos desenvolvedores móveis provavelmente está familiarizada com o SQLite. Ele existe desde 2000 e é sem dúvida o mecanismo de banco de dados relacional mais usado no mundo.

O SQLite tem vários benefícios que todos reconhecemos, um dos quais é o suporte nativo no Android.

O fato de ser um banco de dados relacional SQL padrão também minimiza a curva de aprendizado para quem vem de um background de banco de dados relacional. Ele também fornece um desempenho razoavelmente bom se usado em todo o seu potencial (alavancando recursos, como declarações preparadas, operações em massa com transações etc.). Embora o SQLite possa não ser dimensionado muito bem para todas as suas necessidades.

No entanto, lidar diretamente com instruções SQL tem várias desvantagens.

De acordo com a documentação oficial do Android, aqui estão as etapas necessárias para começar a ler/gravar no SQLite:

  1. Descreva seu esquema em termos de classes de contrato.
  2. Defina seus comandos create/drop table em strings.
  3. Estenda o SQLiteOpenHelper para executar comandos de criação e gerenciar upgrades/downgrades.

Depois de fazer isso, você estará pronto para ler e gravar em seu banco de dados. No entanto, você precisará converter entre os objetos em seu aplicativo e os valores no banco de dados. Para encurtar a história: é muito código clichê!

Outra questão é a manutenção. À medida que seu projeto cresce e surge a necessidade de escrever consultas mais complexas, você acabará com grandes pedaços de consultas SQL brutas em strings. Se mais tarde você precisar alterar a lógica dessas consultas, pode ser um aborrecimento.

Apesar de suas desvantagens, há casos em que usar SQL bruto é sua melhor opção. Um exemplo é quando você está desenvolvendo uma biblioteca em que o desempenho e o tamanho são fatores críticos e a adição de uma biblioteca de terceiros deve ser evitada, se possível.

Mapeador objeto-relacional: o curativo para os desafios do SQL

Para nos salvar de lidar com SQL bruto, os ORMs vieram em socorro.

Alguns dos ORMs Android mais famosos são DBFlow, greenDAO e OrmLite.

O maior valor que eles trazem é a abstração SQLite, permitindo mapear entidades de banco de dados para objetos Java com relativa facilidade.

Entre outros benefícios, os desenvolvedores de aplicativos podem trabalhar com objetos, uma estrutura de dados muito mais familiar. Também ajuda na manutenção, pois agora estamos lidando com objetos de alto nível com uma tipagem mais forte e deixando o trabalho sujo para as bibliotecas. Menos dificuldades com a construção de consultas concatenando strings ou manipulando manualmente a conexão com o banco de dados. Menos erros de digitação.

Embora seja um fato que esses ORMs elevaram o nível dos bancos de dados Android, eles também têm suas desvantagens. Em muitos casos, você acaba carregando dados desnecessários.

Aqui está um exemplo.

Digamos que você tenha uma tabela com 15 colunas e, em uma determinada tela do seu aplicativo, seja exibida uma lista de objetos dessa tabela. Essa lista exibe valores de apenas três colunas. Portanto, ao carregar todos os dados da linha da tabela, você acaba trazendo cinco vezes mais dados do que realmente precisava para aquela tela.

Verdade seja dita, em algumas dessas bibliotecas você pode especificar quais colunas você deseja recuperar antecipadamente, mas para isso você precisa adicionar mais código e, mesmo assim, isso não será suficiente caso você possa saber exatamente quais colunas você deseja use depois de examinar os dados em si: alguns dados podem ser carregados desnecessariamente de qualquer maneira.

Além disso, geralmente há cenários em que você precisa fazer consultas complexas e sua biblioteca ORM simplesmente não oferece uma maneira de descrever essas consultas com sua API. Isso pode fazer você escrever consultas ineficientes que fazem mais cálculos do que você precisa, por exemplo.

A consequência é uma perda de desempenho, levando você a recorrer ao SQL bruto. Embora isso não seja um problema para muitos de nós, isso prejudica o objetivo principal do mapeamento objeto-relacional e nos leva de volta a alguns dos problemas mencionados acima em relação ao SQLite.

Reino: uma alternativa perfeita

O Realm Mobile Database é um banco de dados projetado para dispositivos móveis desde o início.

A principal diferença entre Realm e ORMs é que Realm não é uma abstração construída em cima do SQLite, mas um mecanismo de banco de dados totalmente novo. Em vez de um modelo relacional, ele é baseado em um armazenamento de objetos. Seu núcleo consiste em uma biblioteca C++ independente. Atualmente, ele suporta Android, iOS (Objective-C e Swift), Xamarin e React Native.

O Realm foi lançado em junho de 2014, então atualmente tem dois anos e meio (muito novo!).

Enquanto as tecnologias de banco de dados de servidor estavam passando por uma revolução desde 2007, com muitos novos surgindo, a tecnologia de banco de dados para dispositivos móveis permaneceu presa ao SQLite e seus wrappers. Essa foi uma das principais motivações para criar algo do zero. Além disso, como veremos, alguns dos recursos do Realm exigiam mudanças fundamentais na maneira como um banco de dados se comporta em um nível baixo, e isso simplesmente não era possível construir algo em cima do SQLite.

Mas o Realm realmente vale a pena? Aqui estão as principais razões pelas quais você deve considerar adicionar o Realm ao seu cinto de ferramentas.

Modelagem fácil

Aqui está um exemplo de alguns modelos criados com o Realm:

 public class Contact extends RealmObject { @PrimaryKey String id; protected String name; String email; @Ignore public int sessionId; //Relationships private Address address; private RealmList<Contact> friends; //getters & setter left out for brevity }
 public class Address extends RealmObject { @PrimaryKey public Long id; public String name; public String address; public String city; public String state; public long phone; }

Seus modelos se estendem de RealmObject. Realm aceita todos os tipos primitivos e seus tipos em caixa (exceto char ), String , Date e byte[] . Ele também suporta subclasses de RealmObject e RealmList<? extends RealmObject> RealmList<? extends RealmObject> para modelar relacionamentos.

Os campos podem ter qualquer nível de acesso (privado, público, protegido, etc). Todos os campos são persistidos por padrão, e você só precisa anotar campos “especiais” (por exemplo, @PrimaryKey para seu campo de chave primária, @Ignore para definir campos não persistentes, etc.).

O interessante dessa abordagem é que ela mantém as classes menos “poluídas por anotação” em comparação aos ORMs, pois na maioria deles você precisa de anotações para mapear classes para tabelas, campos regulares para colunas de banco de dados, campos de chave estrangeira para outras tabelas e assim por diante. em.

Relacionamentos

Quando se trata de relacionamentos, existem duas opções:

  • Adicione um modelo como o campo de outro modelo. Em nosso exemplo, a classe Contact contém um campo Address e isso define seu relacionamento. Um contato pode ter um endereço, mas nada impede que esse mesmo endereço seja adicionado a outros contatos. Isso permite relacionamentos um-para-um e um-para-muitos.

  • Adicione uma RealmList dos modelos que estão sendo referenciados. RealmLists se comportam como as boas e velhas Lists Java , agindo como um contêiner de objetos Realm. Podemos ver que nosso modelo Contact tem uma RealmList de contatos, que são seus amigos neste exemplo. Relacionamentos um-para-muitos e muitos-para-muitos podem ser modelados com essa abordagem.

Gosto dessa maneira de representar relacionamentos porque parece muito natural para nós, desenvolvedores Java. Ao adicionar esses objetos (ou listas desses objetos) diretamente como campos de nossa classe, assim como faríamos para outras classes não-modelo, não precisamos lidar com configurações SQLite para chaves estrangeiras.

Advertência: Não há suporte para herança de modelo. A solução atual é usar composição. Portanto, se, por exemplo, você tem um modelo Animal e espera criar um modelo Dog estendendo-se de Animal , terá que adicionar uma instância Animal como um campo em Dog . Há um grande debate sobre Composição vs. Herança. Se você gosta de usar herança, isso é definitivamente algo que você precisa saber sobre o Realm. Com o SQLite, isso pode ser implementado usando duas tabelas (uma para o pai e outra para o filho) conectadas por uma chave estrangeira. Alguns ORMs também não impõem essa restrição, como DBFlow.

Recupere apenas os dados que você precisa! Design de cópia zero

Este é um recurso matador.

O Realm aplica o conceito de design de cópia zero, o que significa que os dados nunca são copiados para a memória. Os resultados obtidos de uma consulta são, na verdade, apenas ponteiros para os dados reais. Os dados em si são carregados lentamente à medida que você os acessa.

Por exemplo, você tem um modelo com 10 campos (colunas em SQL). Se você consultar objetos desse modelo para exibi-los listados em uma tela e precisar apenas de três dos 10 campos para preencher os itens da lista, esses serão os únicos campos recuperados.

Como consequência, as consultas são incrivelmente rápidas (veja aqui e aqui alguns resultados de benchmark).

Essa é uma grande vantagem sobre os ORMs que geralmente carregam todos os dados de linhas SQL selecionadas antecipadamente.

O carregamento de tela se torna muito mais eficiente como resultado, sem exigir mais esforço do desenvolvedor: é apenas o comportamento padrão do Realm.

Além disso, isso também significa que os aplicativos consomem menos memória e, considerando que estamos falando de um ambiente com recursos limitados, como dispositivos móveis, isso pode fazer uma grande diferença.

Outra consequência da abordagem de cópia zero é que os objetos gerenciados pelo Realm são atualizados automaticamente.

Os dados nunca são copiados para a memória. Se você tiver resultados de uma consulta e outro segmento tiver atualizado esses dados no banco de dados após sua consulta, os resultados que você possui já refletirão essas alterações. Seus resultados são apenas indicadores para os dados reais. Assim, quando você acessa valores de campos, os dados mais atualizados são retornados.

Ilustração: Acessando dados do Realm de vários objetos e encadeamentos.

Se você já leu dados de objetos Realm e os exibiu na tela, por exemplo, e deseja receber atualizações para quando os dados subjacentes forem alterados, você pode adicionar um ouvinte:

 final RealmResults<Contact> johns = realm.where(Contact.class).beginsWith("name", "John ").findAll(); johns.addChangeListener(new RealmChangeListener<RealmResults<Contact>>() { @Override public void onChange(RealmResults<Contact> results) { // UPDATE UI } });

Não é apenas um invólucro

Embora tenhamos dezenas de opções para ORMs, eles são wrappers, e tudo se resume ao SQLite, o que limita o quão longe eles podem chegar. Em contraste, o Realm não é apenas mais um wrapper SQLite. Ele tem a liberdade de fornecer recursos que os ORMs simplesmente não podem oferecer.

Uma das mudanças fundamentais com o Realm é poder armazenar dados como um armazenamento de gráfico de objetos.

Isso significa que Realm é objetos em todo o caminho, desde o nível da linguagem de programação até o banco de dados. Conseqüentemente, há muito menos conversão sendo feita à medida que você escreve e lê valores, em comparação com um banco de dados relacional.

As estruturas de banco de dados refletem mais de perto as estruturas de dados que os desenvolvedores de aplicativos usam. Na verdade, essa é uma das principais razões pelas quais há um movimento de afastamento da modelagem relacional e em direção a modelos agregados no desenvolvimento do lado do servidor. A Realm finalmente traz algumas dessas ideias para o mundo do desenvolvimento móvel.

Se pensarmos nos componentes da arquitetura do Realm, na parte inferior está o seu núcleo com a implementação mais fundamental da plataforma. Além disso, teremos bibliotecas de vinculação para cada plataforma suportada.

Ilustração: Arquitetura do Realm desde o núcleo até várias bibliotecas.

Ao usar um wrapper para alguma tecnologia sobre a qual você não tem controle, eventualmente será necessário fornecer algum tipo de camada de abstração em torno dele.

As bibliotecas de vinculação de realm são projetadas para serem o mais finas possível, para eliminar a complexidade da abstração. Eles propagam principalmente a ideia de design do Core. Ao ter o controle de toda a arquitetura, esses componentes funcionam em melhor sincronia entre si.

Um exemplo prático é o acesso a outros objetos referenciados (chaves estrangeiras em SQL). A estrutura de arquivos do Realm é baseada em links nativos, portanto, quando você consulta relacionamentos, em vez de traduzir uma abstração ORM para relacional e/ou juntar várias tabelas, você obtém links brutos para os objetos em um nível de sistema de arquivos no formato de arquivo.

São objetos apontando diretamente para outros objetos. Assim, consultar um relacionamento é o mesmo que consultar uma coluna inteira, por exemplo. Não há necessidade de operações caras atravessando chaves estrangeiras. É tudo sobre seguir ponteiros.

Suporte da comunidade

O Realm está em desenvolvimento ativo e lança versões atualizadas com bastante frequência.

Todos os componentes do Realm Mobile Database são de código aberto. Eles são muito responsivos no rastreador de problemas e no Stack Overflow, então você pode esperar um suporte bom e rápido nesses canais.

Além disso, o feedback da comunidade é levado em consideração ao priorizar problemas (bugs, melhorias, solicitações de recursos, etc.). É sempre bom saber que você pode ter uma palavra a dizer no desenvolvimento das ferramentas que você usa.

Comecei a usar o Realm em 2015 e, desde então, esbarrei em vários posts na web com várias opiniões sobre o Realm. Falaremos sobre suas limitações em breve, mas uma coisa que notei é que muitas das reclamações feitas no momento do post já foram corrigidas.

Quando conheci o Realm, por exemplo, ainda não havia suporte para métodos personalizados em modelos e chamadas assíncronas. Estes foram disjuntores para muitos na época, mas ambos são suportados atualmente.

Essa velocidade de desenvolvimento e capacidade de resposta nos deixam mais confiantes de que não esperaremos muito por recursos importantes.

Limitações

Como tudo na vida, o Reino não é só rosas. Além da limitação de herança mencionada anteriormente, existem outras deficiências a serem observadas:

  • Embora seja possível ter vários encadeamentos lendo e gravando no banco de dados ao mesmo tempo, os objetos Realm não podem ser movidos entre encadeamentos . Portanto, se, por exemplo, você recuperar um objeto de domínio usando doInBackground doInBackground() do AsyncTask, que é executado em um thread em segundo plano, não poderá passar essa instância para os métodos onPostExecute() , pois eles são executados no thread principal. As possíveis soluções para essa situação seriam fazer uma cópia do objeto e passá-lo adiante ou passar o id do objeto e recuperar o objeto novamente em onPostExecute() . O Realm oferece métodos síncronos e assíncronos para leitura/gravação.

  • Não há suporte para chaves primárias de incremento automático , portanto, você precisará lidar com a geração delas por conta própria.

  • Não é possível acessar o banco de dados de processos distintos ao mesmo tempo . De acordo com a documentação deles, o suporte multiprocesso será lançado em breve.

Realm é o futuro das soluções de banco de dados móvel

O SQLite é um mecanismo de banco de dados sólido, robusto e comprovado, e os bancos de dados relacionais não desaparecerão tão cedo. Existem vários ORMs bons por aí que também funcionarão em muitos cenários.

No entanto, é importante manter-se atualizado sobre as tendências atuais.

A esse respeito, acho que o Realm é uma das maiores tendências nos últimos anos quando se trata de desenvolvimento de banco de dados móvel.

O Realm traz consigo uma abordagem única para lidar com dados valiosos para os desenvolvedores, não apenas porque pode ser uma opção melhor do que as soluções existentes, mas também porque amplia nossos horizontes em termos de novas possibilidades e eleva o nível da tecnologia de banco de dados móvel.

Você já tem experiência com o Realm? Por favor, sinta-se livre para compartilhar seus pensamentos.