Realm est la meilleure solution de base de données Android

Publié: 2022-03-11

Depuis la création d'Android, nous, les développeurs d'applications, utilisons SQLite pour stocker nos données locales. Parfois directement avec des instructions SQL, parfois en utilisant un ORM (Object-Relational Mapper) comme couche d'abstraction, mais dans tous les cas, nous avons utilisé SQLite à la fin de la journée.

Malgré tous les avantages de SQLite, il y a eu des moments où nous souhaitions avoir des alternatives à un modèle relationnel : quelque chose qui pourrait nous éviter d'avoir à ajouter du code passe-partout pour convertir des valeurs vers et depuis la base de données, ou nous permettre d'ignorer la configuration des mappages entre classes et tables, champs et colonnes, clés étrangères, etc.

En d'autres termes, une base de données avec des structures de données plus similaires à celles que nous utilisons réellement au niveau de l'application. Mieux encore, s'il pouvait être efficace en mémoire par conception, permettant de meilleures expériences dans les appareils à ressources limitées, ce serait génial.

Ce sont, en fait, quelques-uns des avantages prêts à l'emploi que nous obtenons avec Realm, une plate-forme de base de données avec une architecture distincte, qui est apparue comme une nouvelle alternative à SQLite.

Cet article présente certaines des principales raisons pour lesquelles Realm a attiré autant d'attention et pourquoi vous voudrez peut-être envisager de l'essayer. Il traite de certains des principaux avantages que Realm offre aux développeurs Android par rapport à SQLite.

Étant donné que Realm est disponible sur plusieurs plates-formes, une partie de ce qui sera couvert dans cet article est également pertinente pour d'autres plates-formes mobiles, telles que iOS, Xamarin et React Native.

SQLite : ça marche, mais ce n'est pas ce dont vous avez besoin la plupart du temps

La plupart des développeurs mobiles connaissent probablement SQLite. Il existe depuis 2000 et c'est sans doute le moteur de base de données relationnelle le plus utilisé au monde.

SQLite présente un certain nombre d'avantages que nous reconnaissons tous, dont l'un est sa prise en charge native sur Android.

Le fait qu'il s'agisse d'une base de données relationnelle SQL standard minimise également la courbe d'apprentissage pour ceux qui viennent d'une base de données relationnelle. Il fournit également des performances raisonnablement bonnes s'il est utilisé à son plein potentiel (en tirant parti des fonctionnalités, telles que les déclarations préparées, les opérations en masse avec les transactions, etc.). Bien que SQLite puisse ne pas s'adapter très bien à tous vos besoins.

Le traitement direct des instructions SQL présente cependant un certain nombre d'inconvénients.

Selon la documentation officielle d'Android, voici les étapes nécessaires pour commencer à lire/écrire sur SQLite :

  1. Décrivez votre schéma en termes de classes de contrat.
  2. Définissez vos commandes de création/suppression de table dans des chaînes.
  3. Étendez SQLiteOpenHelper pour exécuter les commandes de création et gérer les mises à niveau/rétrogradations.

Une fois que vous avez fait cela, vous serez prêt à lire et à écrire dans votre base de données. Cependant, vous devrez effectuer des conversions entre les objets de votre application et les valeurs de la base de données. Pour faire court : c'est beaucoup de code passe-partout !

Un autre problème est la maintenabilité. Au fur et à mesure que votre projet grandit et que le besoin d'écrire des requêtes plus complexes se fait sentir, vous vous retrouverez avec de gros morceaux de requêtes SQL brutes dans des chaînes. Si plus tard vous avez besoin de changer la logique de ces requêtes, cela peut être assez compliqué.

Malgré ses inconvénients, il existe des cas où l'utilisation de SQL brut est votre meilleure option. Un exemple est lorsque vous développez une bibliothèque où les performances et la taille sont des facteurs critiques et l'ajout d'une bibliothèque tierce doit être évité si possible.

Mappeur objet-relationnel : le pansement pour les défis SQL

Pour nous éviter de traiter avec du SQL brut, les ORM sont venus à la rescousse.

Certains des ORM Android les plus connus sont DBFlow, greenDAO et OrmLite.

La plus grande valeur qu'ils apportent est l'abstraction SQLite, nous permettant de mapper relativement facilement des entités de base de données sur des objets Java.

Entre autres avantages, les développeurs d'applications peuvent travailler avec des objets, une structure de données beaucoup plus familière. Cela aide également à la maintenabilité, car nous traitons maintenant des objets de haut niveau avec un typage plus fort et laissons le sale boulot aux bibliothèques. Moins de difficulté à créer des requêtes en concaténant des chaînes ou en gérant manuellement la connexion avec la base de données. Moins de fautes de frappe.

Bien qu'il soit un fait que ces ORM ont élevé la barre sur les bases de données Android, ils ont aussi leurs inconvénients. Dans de nombreux cas, vous finissez par charger des données inutiles.

Voici un exemple.

Supposons que vous ayez un tableau de 15 colonnes et que, dans un certain écran de votre application, une liste d'objets de ce tableau s'affiche. Cette liste affiche les valeurs de seulement trois colonnes. Par conséquent, en chargeant toutes les données de la ligne du tableau, vous finissez par apporter cinq fois plus de données que ce dont vous avez réellement besoin pour cet écran.

À vrai dire, dans certaines de ces bibliothèques, vous pouvez spécifier les colonnes que vous souhaitez récupérer à l'avance, mais pour cela, vous devez ajouter du code supplémentaire, et même ainsi, cela ne suffira pas si vous ne pouvez savoir exactement quelles colonnes vous allez à utiliser après avoir examiné les données elles-mêmes : certaines données peuvent de toute façon être chargées inutilement.

De plus, il existe souvent des scénarios dans lesquels vous avez des requêtes complexes à effectuer, et votre bibliothèque ORM ne vous offre tout simplement pas un moyen de décrire ces requêtes avec son API. Cela peut vous amener à écrire des requêtes inefficaces qui effectuent plus de calculs que ce dont vous avez besoin, par exemple.

La conséquence est une perte de performance, vous amenant à recourir au SQL brut. Bien que ce ne soit pas une rupture pour beaucoup d'entre nous, cela nuit à l'objectif principal du mappage objet-relationnel et nous ramène à certains des problèmes susmentionnés concernant SQLite.

Royaume : une alternative parfaite

Realm Mobile Database est une base de données entièrement conçue pour les appareils mobiles.

La principale différence entre Realm et ORM est que Realm n'est pas une abstraction construite au-dessus de SQLite, mais un tout nouveau moteur de base de données. Plutôt qu'un modèle relationnel, il est basé sur un magasin d'objets. Son cœur est constitué d'une bibliothèque C++ autonome. Il prend actuellement en charge Android, iOS (Objective-C et Swift), Xamarin et React Native.

Realm a été lancé en juin 2014, il a donc actuellement deux ans et demi (assez nouveau !).

Alors que les technologies de base de données de serveur traversaient une révolution depuis 2007, avec l'émergence de nombreuses nouvelles technologies, la technologie de base de données pour les appareils mobiles est restée bloquée avec SQLite et ses wrappers. C'était l'une des principales motivations pour créer quelque chose à partir de zéro. De plus, comme nous le verrons, certaines des fonctionnalités de Realm nécessitaient des changements fondamentaux dans la façon dont une base de données se comporte à un niveau bas, et ce n'était tout simplement pas possible de construire quelque chose au-dessus de SQLite.

Mais Realm en vaut-il vraiment la peine ? Voici les principales raisons pour lesquelles vous devriez envisager d'ajouter Realm à votre ceinture à outils.

Modélisation facile

Voici un exemple de modèles créés avec 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; }

Vos modèles s'étendent de RealmObject. Realm accepte tous les types primitifs et ses types encadrés (à l'exception de char ), String , Date et byte[] . Il prend également en charge les sous-classes de RealmObject et RealmList<? extends RealmObject> RealmList<? extends RealmObject> pour modéliser les relations.

Les champs peuvent avoir n'importe quel niveau d'accès (privé, public, protégé, etc.). Tous les champs sont persistants par défaut, et il vous suffit d'annoter les champs "spéciaux" (par exemple, @PrimaryKey pour votre champ de clé primaire, @Ignore pour définir des champs non persistants, etc.).

La chose intéressante à propos de cette approche est qu'elle maintient les classes moins "polluées par les annotations" par rapport aux ORM, car dans la plupart d'entre elles, vous avez besoin d'annotations pour mapper des classes sur des tables, des champs réguliers sur des colonnes de base de données, des champs de clé étrangère sur d'autres tables, etc. au.

Des relations

En ce qui concerne les relations, il y a deux options :

  • Ajoutez un modèle en tant que champ d'un autre modèle. Dans notre exemple, la classe Contact contient un champ Address et qui définit leur relation. Un contact peut avoir une adresse, mais rien n'empêche que cette même adresse soit ajoutée à d'autres contacts. Cela permet des relations un à un et un à plusieurs.

  • Ajoutez une RealmList des modèles référencés. RealmLists se comportent un peu comme les bonnes vieilles Java Lists , agissant comme un conteneur d'objets Realm. Nous pouvons voir que notre modèle Contact a une RealmList de contacts, qui sont ses amis dans cet exemple. Les relations un-à-plusieurs et plusieurs-à-plusieurs peuvent être modélisées avec cette approche.

J'aime cette façon de représenter les relations car elle semble très naturelle pour nous, développeurs Java. En ajoutant ces objets (ou des listes de ces objets) directement en tant que champs de notre classe, comme nous le ferions pour d'autres classes non modèles, nous n'avons pas besoin de gérer les paramètres SQLite pour les clés étrangères.

Mise en garde : Il n'y a pas de prise en charge de l'héritage de modèle. La solution de contournement actuelle consiste à utiliser la composition. Ainsi, si, par exemple, vous avez un modèle Animal et espériez créer un modèle Dog s'étendant à partir de Animal , vous devrez à la place ajouter une instance Animal en tant que champ dans Dog . Il y a un grand débat sur la composition par rapport à l'héritage. Si vous aimez utiliser l'héritage, c'est certainement quelque chose que vous devez savoir sur Realm. Avec SQLite, cela pourrait être implémenté en utilisant deux tables (une pour le parent et une pour l'enfant) connectées par une clé étrangère. Certains ORM n'imposent pas non plus cette restriction, comme DBFlow.

Récupérez uniquement les données dont vous avez besoin ! Conception sans copie

C'est une fonctionnalité qui tue.

Realm applique le concept de conception sans copie, ce qui signifie que les données ne sont jamais copiées dans la mémoire. Les résultats que vous obtenez à partir d'une requête ne sont en fait que des pointeurs vers les données réelles. Les données elles-mêmes sont chargées paresseusement lorsque vous y accédez.

Par exemple, vous avez un modèle avec 10 champs (colonnes en SQL). Si vous recherchez des objets de ce modèle pour les afficher sur un écran et que vous n'avez besoin que de trois des 10 champs pour remplir les éléments de la liste, ce seront les seuls champs récupérés.

En conséquence, les requêtes sont extrêmement rapides (voir ici et ici pour quelques résultats de référence).

C'est un gros avantage par rapport aux ORM qui chargent généralement toutes les données des lignes SQL sélectionnées à l'avance.

Le chargement de l'écran devient ainsi beaucoup plus efficace sans nécessiter d'effort supplémentaire de la part du développeur : c'est juste le comportement par défaut de Realm.

De plus, cela signifie également que les applications consomment moins de mémoire et, étant donné que nous parlons d'un environnement aux ressources limitées comme les appareils mobiles, cela peut faire une grande différence.

Une autre conséquence de l'approche zéro copie est que les objets gérés par Realm sont automatiquement mis à jour.

Les données ne sont jamais copiées dans la mémoire. Si vous avez des résultats d'une requête et qu'un autre thread a mis à jour ces données sur la base de données après votre requête, les résultats que vous possédez refléteront déjà ces modifications. Vos résultats ne sont que des pointeurs vers les données réelles. Ainsi, lorsque vous accédez aux valeurs des champs, les données les plus récentes sont renvoyées.

Illustration : Accès aux données du domaine à partir de plusieurs objets et threads.

Si vous avez déjà lu des données à partir d'objets Realm et les avez affichées à l'écran, par exemple, et souhaitez recevoir des mises à jour lorsque les données sous-jacentes changent, vous pouvez ajouter un écouteur :

 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 } });

Ce n'est pas qu'un emballage

Bien que nous ayons des dizaines d'options pour les ORM, ce sont des wrappers, et tout se résume à SQLite en dessous, ce qui limite jusqu'où ils peuvent aller. En revanche, Realm n'est pas simplement un autre wrapper SQLite. Il a la liberté de fournir des fonctionnalités que les ORM ne peuvent tout simplement pas offrir.

L'un des changements fondamentaux avec Realm est la possibilité de stocker des données en tant que magasin de graphes d'objets.

Cela signifie que Realm est composé d'objets jusqu'en bas, du niveau du langage de programmation à la base de données. Par conséquent, il y a beaucoup moins de conversions effectuées dans les deux sens lorsque vous écrivez et lisez des valeurs, par rapport à une base de données relationnelle.

Les structures de base de données reflètent plus fidèlement les structures de données utilisées par les développeurs d'applications. En fait, c'est l'une des principales raisons pour lesquelles on s'éloigne de la modélisation relationnelle au profit de modèles agrégés pour le développement côté serveur. Realm apporte enfin certaines de ces idées au monde du développement mobile.

Si nous pensons aux composants de l'architecture de Realm, en bas se trouve son noyau avec l'implémentation la plus fondamentale de la plate-forme. En plus de cela, nous aurons des bibliothèques de liaison à chaque plate-forme prise en charge.

Illustration : architecture de domaine du noyau à diverses bibliothèques.

Lorsque vous utilisez un wrapper pour une technologie sur laquelle vous n'avez aucun contrôle, vous devez éventuellement fournir une sorte de couche d'abstraction autour d'elle.

Les bibliothèques de liaison de domaine sont conçues pour être aussi fines que possible, afin de réduire la complexité de l'abstraction. Ils propagent principalement l'idée de conception de Core. En ayant le contrôle de l'ensemble de l'architecture, ces composants fonctionnent en meilleure synchronisation les uns avec les autres.

Un exemple pratique est l'accès à d'autres objets référencés (clés étrangères en SQL). La structure de fichiers de Realm est basée sur des liens natifs, donc lorsque vous recherchez des relations, plutôt que d'avoir à traduire une abstraction ORM en relationnel et/ou à joindre plusieurs tables, vous obtenez des liens bruts vers les objets au niveau du système de fichiers dans le format de fichier.

Ce sont des objets pointant directement vers d'autres objets. Ainsi, interroger une relation revient à interroger une colonne d'entiers, par exemple. Pas besoin d'opérations coûteuses traversant des clés étrangères. Il s'agit de suivre les pointeurs.

Communauté et assistance

Realm est en cours de développement et publie assez souvent des versions mises à jour.

Tous les composants de la base de données Realm Mobile sont open-source. Ils sont très réactifs sur leur outil de suivi des problèmes et Stack Overflow, vous pouvez donc vous attendre à un support rapide et de qualité sur ces canaux.

De plus, les commentaires de la communauté sont pris en compte lors de la hiérarchisation des problèmes (bugs, améliorations, demandes de fonctionnalités, etc.). Il est toujours bon de savoir que vous pouvez avoir votre mot à dire dans le développement des outils que vous utilisez.

J'ai commencé à utiliser Realm en 2015, et depuis lors, je suis tombé sur plusieurs publications sur le Web avec diverses opinions sur Realm. Nous parlerons bientôt de ses limites, mais une chose que j'ai remarquée est que de nombreuses plaintes formulées au moment de la publication ont depuis été corrigées.

Lorsque j'ai découvert Realm, par exemple, les méthodes personnalisées sur les modèles et les appels asynchrones n'étaient pas encore prises en charge. C'étaient des facteurs décisifs pour beaucoup à l'époque, mais les deux sont actuellement pris en charge.

Une telle vitesse de développement et une telle réactivité nous rendent plus confiants que nous n'attendrons pas longtemps pour des fonctionnalités importantes.

Limites

Comme pour tout dans la vie, Realm n'est pas que des roses. Outre la limitation de l'héritage mentionnée précédemment, il y a d'autres lacunes à garder à l'esprit :

  • Bien qu'il soit possible que plusieurs threads lisent et écrivent dans la base de données en même temps, les objets Realm ne peuvent pas être déplacés entre les threads . Ainsi, si, par exemple, vous récupérez un objet de domaine à l'aide de doInBackground() d'AsyncTask, qui s'exécute dans un thread d'arrière-plan, vous ne pouvez pas transmettre cette instance aux méthodes onPostExecute() , car celles-ci s'exécutent sur le thread principal. Les solutions de contournement possibles pour cette situation seraient soit de faire une copie de l'objet et de la transmettre, soit de transmettre l'identifiant de l'objet et de récupérer à nouveau l'objet sur onPostExecute() . Realm propose des méthodes synchrones et asynchrones pour la lecture/écriture.

  • Il n'y a pas de prise en charge des clés primaires à incrémentation automatique , vous devrez donc gérer vous-même la génération de celles-ci.

  • Il n'est pas possible d'accéder à la base de données à partir de processus distincts en même temps . Selon leur documentation, le support multi-processus arrive bientôt.

Realm est l'avenir des solutions de bases de données mobiles

SQLite est un moteur de base de données solide, robuste et éprouvé, et les bases de données relationnelles ne vont pas disparaître de si tôt. Il existe un certain nombre de bons ORM qui feront également l'affaire pour de nombreux scénarios.

Cependant, il est important de se tenir au courant des tendances actuelles.

À cet égard, je pense que Realm est l'une des plus grandes tendances à venir ces dernières années en matière de développement de bases de données mobiles.

Realm apporte avec lui une approche unique pour traiter les données qui sont précieuses pour les développeurs, non seulement parce qu'elles peuvent être une meilleure option que les solutions existantes, mais aussi parce qu'elles élargissent nos horizons en termes de nouvelles possibilités et placent la barre plus haut sur la technologie des bases de données mobiles.

Avez-vous déjà de l'expérience avec Realm ? N'hésitez pas à partager vos réflexions.