Realm è la migliore soluzione di database Android
Pubblicato: 2022-03-11Da quando è stato creato Android, noi sviluppatori di app utilizziamo SQLite per archiviare i nostri dati locali. A volte direttamente con istruzioni SQL, a volte utilizzando un Object-Relational Mapper (ORM) come livello di astrazione, ma in entrambi i casi, alla fine della giornata abbiamo utilizzato SQLite.
Nonostante tutti i vantaggi di SQLite, tuttavia, ci sono stati momenti in cui avremmo voluto avere alternative a un modello relazionale: qualcosa che potesse evitarci di dover aggiungere codice standard per convertire i valori da e verso il database, o consentirci di saltare l'impostazione delle mappature tra classi e tabelle, campi e colonne, chiavi esterne, ecc.
In altre parole, un database con strutture dati più simili a quelle che effettivamente utilizziamo a livello applicativo. Meglio ancora, se potesse essere efficiente in termini di memoria in base alla progettazione, consentendo esperienze migliori in dispositivi con risorse limitate, sarebbe fantastico.
Questi sono, in effetti, alcuni dei vantaggi pronti all'uso che otteniamo con Realm, una piattaforma di database con un'architettura distinta, che è emersa come una nuova alternativa a SQLite.
Questo articolo presenta alcuni dei motivi principali per cui Realm ha attirato così tanta attenzione e perché potresti prendere in considerazione l'idea di provarlo. Discute alcuni dei vantaggi chiave che Realm offre agli sviluppatori Android su SQLite.
Poiché Realm è disponibile su più piattaforme, parte di ciò che verrà trattato in questo articolo ha rilevanza anche per altre piattaforme mobili, come iOS, Xamarin e React Native.
SQLite: Funziona, ma non è ciò di cui hai bisogno la maggior parte del tempo
La maggior parte degli sviluppatori mobili ha probabilmente familiarità con SQLite. È in circolazione dal 2000 ed è probabilmente il motore di database relazionale più utilizzato al mondo.
SQLite ha una serie di vantaggi che tutti riconosciamo, uno dei quali è il suo supporto nativo su Android.
Il fatto che si tratti di un database relazionale SQL standard riduce anche al minimo la curva di apprendimento per coloro che provengono da un background di database relazionali. Fornisce inoltre prestazioni ragionevolmente buone se utilizzato al massimo del suo potenziale (sfruttando funzionalità, come dichiarazioni preparate, operazioni in blocco con transazioni, ecc.). Sebbene SQLite potrebbe non adattarsi molto bene a tutte le tue esigenze.
Tuttavia, trattare direttamente con le istruzioni SQL ha una serie di aspetti negativi.
Secondo la documentazione ufficiale di Android, ecco i passaggi necessari per iniziare a leggere/scrivere su SQLite:
- Descrivi il tuo schema in termini di classi di contratto.
- Definisci i comandi di creazione/rilascio della tabella nelle stringhe.
- Estendi
SQLiteOpenHelperper eseguire creare comandi e gestire aggiornamenti/downgrade.
Una volta fatto questo, sarai pronto per leggere e scrivere nel tuo database. Tuttavia, dovrai convertire avanti e indietro tra gli oggetti nell'applicazione e i valori nel database. Per farla breve: è un sacco di codice standard!
Un altro problema è la manutenibilità. Man mano che il tuo progetto diventa più grande e sorge la necessità di scrivere query più complesse, ti ritroverai con grandi porzioni di query SQL grezze in stringhe. Se in seguito è necessario modificare la logica di tali query, può essere piuttosto una seccatura.
Nonostante i suoi aspetti negativi, ci sono casi in cui l'utilizzo di SQL non elaborato è l'opzione migliore. Un esempio è lo sviluppo di una libreria in cui le prestazioni e le dimensioni sono fattori critici e l'aggiunta di una libreria di terze parti dovrebbe essere evitata, se possibile.
Object-Relational Mapper: il cerotto per le sfide SQL
Per salvarci dall'affrontare SQL grezzo, gli ORM sono venuti in soccorso.
Alcuni degli ORM Android più famosi sono DBFlow, greenDAO e OrmLite.
Il valore più grande che apportano è l'astrazione SQLite, che ci consente di mappare le entità del database su oggetti Java in modo relativamente semplice.
Tra gli altri vantaggi, gli sviluppatori di applicazioni possono lavorare con gli oggetti, una struttura dati molto più familiare. Aiuta anche con la manutenibilità, poiché ora stiamo gestendo oggetti di alto livello con una digitazione più forte e lasciando il lavoro sporco alle librerie. Meno difficoltà con la creazione di query concatenando stringhe o gestendo manualmente la connessione con il database. Meno errori di battitura.
Sebbene sia un dato di fatto che questi ORM abbiano alzato il livello dei database Android, hanno anche i loro svantaggi. In molti casi, finisci per caricare dati non necessari.
Ecco un esempio.
Supponi di avere una tabella con 15 colonne e in una determinata schermata della tua app viene visualizzato un elenco di oggetti da questa tabella. Questo elenco visualizza i valori di sole tre colonne. Pertanto, caricando tutti i dati dalla riga della tabella, si ottengono cinque volte più dati di quelli effettivamente necessari per quella schermata.
A dire il vero, in alcune di queste librerie puoi specificare quali colonne vuoi recuperare in anticipo, ma per questo devi aggiungere altro codice, e anche così, ciò non sarà sufficiente nel caso tu possa sapere esattamente quali colonne vuoi utilizzare dopo aver esaminato i dati stessi: alcuni dati potrebbero comunque essere caricati inutilmente.
Inoltre, ci sono spesso scenari in cui devi eseguire query complesse e la tua libreria ORM semplicemente non ti offre un modo per descrivere queste query con la sua API. Ciò può farti scrivere query inefficienti che eseguono più calcoli di quelli necessari, ad esempio.
La conseguenza è una perdita di prestazioni, che ti porta a ricorrere all'SQL grezzo. Sebbene questo non sia un problema per molti di noi, danneggia lo scopo principale della mappatura relazionale degli oggetti e ci riporta ad alcuni dei suddetti problemi relativi a SQLite.
Regno: un'alternativa perfetta
Realm Mobile Database è un database progettato per dispositivi mobili da zero.
La differenza fondamentale tra Realm e ORM è che Realm non è un'astrazione basata su SQLite, ma un motore di database completamente nuovo. Piuttosto che un modello relazionale, si basa su un negozio di oggetti. Il suo nucleo è costituito da una libreria C++ autonoma. Attualmente supporta Android, iOS (Obiettivo-C e Swift), Xamarin e React Native.
Realm è stato lanciato a giugno 2014, quindi attualmente ha due anni e mezzo (piuttosto nuovo!).
Mentre le tecnologie di database per server stavano attraversando una rivoluzione dal 2007, con l'emergere di molte nuove, la tecnologia di database per dispositivi mobili è rimasta bloccata con SQLite e i suoi wrapper. Questa è stata una delle motivazioni chiave per creare qualcosa da zero. Inoltre, come vedremo, alcune delle funzionalità di Realm richiedevano modifiche fondamentali al modo in cui un database si comporta a un livello basso, e ciò semplicemente non era possibile costruendo qualcosa su SQLite.
Ma ne vale davvero la pena? Ecco i principali motivi per cui dovresti considerare di aggiungere Realm alla tua cintura degli attrezzi.
Modellazione facile
Ecco un esempio di alcuni modelli creati con 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; } I tuoi modelli si estendono da RealmObject. Realm accetta tutti i tipi primitivi e i suoi tipi boxed (tranne char ), String , Date e byte[] . Supporta anche le sottoclassi di RealmObject e RealmList<? extends RealmObject> RealmList<? extends RealmObject> per modellare le relazioni.
I campi possono avere qualsiasi livello di accesso (privato, pubblico, protetto, ecc.). Tutti i campi sono persistenti per impostazione predefinita e devi solo annotare i campi "speciali" (ad esempio, @PrimaryKey per il campo della chiave primaria, @Ignore per impostare i campi non persistenti, ecc.).
La cosa interessante di questo approccio è che mantiene le classi meno "inquinate da annotazioni" rispetto agli ORM, poiché nella maggior parte di esse sono necessarie annotazioni per mappare le classi alle tabelle, i campi regolari alle colonne del database, i campi della chiave esterna ad altre tabelle e così via su.
Relazioni
Quando si tratta di relazioni, ci sono due opzioni:
Aggiungi un modello come campo da un altro modello. Nel nostro esempio, la classe
Contactcontiene un campoAddresse che definisce la loro relazione. Un contatto potrebbe avere un indirizzo, ma nulla impedisce che questo stesso indirizzo venga aggiunto ad altri contatti. Ciò consente relazioni uno-a-uno e uno-a-molti.Aggiungi un
RealmListdei modelli a cui si fa riferimento.RealmListssi comportano come le vecchie JavaLists, agendo come un contenitore di oggetti Realm. Possiamo vedere che il nostro modelloContactha unRealmListdi contatti, che sono i suoi amici in questo esempio. Le relazioni uno-a-molti e molti-a-molti possono essere modellate con questo approccio.
Mi piace questo modo di rappresentare le relazioni perché è molto naturale per noi sviluppatori Java. Aggiungendo questi oggetti (o elenchi di questi oggetti) direttamente come campi della nostra classe, proprio come faremmo per altre classi non modello, non abbiamo bisogno di gestire le impostazioni SQLite per le chiavi esterne.
Avvertenza: non esiste supporto per l'ereditarietà del modello. L'attuale soluzione consiste nell'usare la composizione. Quindi, se, ad esempio, hai un modello Animal e speravi di creare un modello Dog che si estende da Animal , dovrai invece aggiungere un'istanza Animal come campo in Dog . C'è un grande dibattito su Composizione vs. Ereditarietà. Se stai usando l'ereditarietà, questo è sicuramente qualcosa che devi sapere su Realm. Con SQLite, questo potrebbe essere implementato utilizzando due tabelle (una per il genitore e una per il figlio) collegate da una chiave esterna. Inoltre, alcuni ORM non impongono questa restrizione, come DBFlow.
Recupera solo i dati di cui hai bisogno! Design a copia zero
Questa è una caratteristica killer.
Realm applica il concetto di progettazione a copia zero, il che significa che i dati non vengono mai copiati in memoria. I risultati che ottieni da una query sono in realtà solo puntatori ai dati reali. I dati stessi vengono caricati pigramente durante l'accesso.
Ad esempio, hai un modello con 10 campi (colonne in SQL). Se esegui una query per gli oggetti di questo modello per visualizzarli elencati su uno schermo e hai solo bisogno di tre dei 10 campi per riempire gli elementi dell'elenco, quelli saranno gli unici campi recuperati.
Di conseguenza, le query sono incredibilmente veloci (vedi qui e qui per alcuni risultati di benchmark).
Questo è un grande vantaggio rispetto agli ORM che di solito caricano in anticipo tutti i dati dalle righe SQL selezionate.
Di conseguenza, il caricamento dello schermo diventa molto più efficiente senza richiedere ulteriori sforzi da parte dello sviluppatore: è solo il comportamento predefinito di Realm.
Inoltre, ciò significa anche che le app consumano meno memoria e, considerando che stiamo parlando di un ambiente con risorse limitate come i dispositivi mobili, ciò può fare una grande differenza.
Un'altra conseguenza dell'approccio zero-copy è che gli oggetti gestiti da Realm vengono aggiornati automaticamente.
I dati non vengono mai copiati in memoria. Se hai risultati da una query e un altro thread ha aggiornato questi dati sul database dopo la tua query, i risultati che possiedi rifletteranno già queste modifiche. I tuoi risultati sono solo puntatori ai dati effettivi. Pertanto, quando si accede ai valori dai campi, vengono restituiti i dati più aggiornati.
Se hai già letto i dati dagli oggetti Realm e li hai visualizzati sullo schermo, ad esempio, e desideri ricevere aggiornamenti per quando i dati sottostanti cambiano, puoi aggiungere un listener:
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 } });Non è solo un involucro
Sebbene disponiamo di dozzine di opzioni per gli ORM, sono wrapper e tutto si riduce a SQLite sottostante, il che limita quanto possono arrivare. Al contrario, Realm non è solo un altro wrapper SQLite. Ha la libertà di fornire funzionalità che gli ORM non possono offrire.
Uno dei cambiamenti fondamentali con Realm è la possibilità di archiviare i dati come archivio di grafici a oggetti.
Ciò significa che Realm è oggetti fino in fondo, dal livello del linguaggio di programmazione al database. Di conseguenza, la conversione avanti e indietro durante la scrittura e la lettura dei valori è molto inferiore rispetto a un database relazionale.
Le strutture di database riflettono più da vicino le strutture di dati utilizzate dagli sviluppatori di applicazioni. In effetti, questo è uno dei motivi principali per cui c'è un allontanamento dalla modellazione relazionale e verso modelli aggregati sullo sviluppo lato server. Realm porta finalmente alcune di queste idee nel mondo dello sviluppo mobile.
Se pensiamo ai componenti nell'architettura di Realm, in fondo c'è il suo core con l'implementazione più fondamentale della piattaforma. Inoltre, avremo librerie di associazione a ciascuna piattaforma supportata.
Quando si utilizza un wrapper per una tecnologia su cui non si ha alcun controllo, alla fine è necessario fornire una sorta di livello di astrazione attorno ad esso.
Le librerie di binding dei reami sono progettate per essere il più sottili possibile, per eliminare la complessità dell'astrazione. Per lo più diffondono l'idea di design da Core. Avendo il controllo dell'intera architettura, questi componenti funzionano in migliore sincronia tra loro.
Un esempio pratico è l'accesso ad altri oggetti referenziati (chiavi esterne in SQL). La struttura dei file di Realm si basa su collegamenti nativi, quindi quando si interrogano le relazioni, invece di dover tradurre un'astrazione ORM in tabelle relazionali e/o unire più tabelle, si ottengono collegamenti grezzi agli oggetti a livello di file system nel formato del file.
Sono oggetti che puntano direttamente ad altri oggetti. Pertanto, interrogare una relazione equivale, ad esempio, a interrogare una colonna intera. Non c'è bisogno di costose operazioni che attraversano le chiavi esterne. Si tratta di seguire i puntatori.
Comunità e supporto
Realm è in fase di sviluppo attivo e ha rilasciato versioni aggiornate abbastanza spesso.
Tutti i componenti del database Realm Mobile sono open source. Sono molto reattivi sul tracker dei problemi e sull'overflow dello stack, quindi puoi aspettarti un supporto buono e veloce su questi canali.
Inoltre, il feedback della community viene preso in considerazione quando si assegna la priorità ai problemi (bug, miglioramenti, richieste di funzionalità, ecc.). È sempre bello sapere che puoi avere voce in capitolo nello sviluppo degli strumenti che utilizzi.
Ho iniziato a usare Realm nel 2015 e da allora mi sono imbattuto in diversi post sul web con varie opinioni su Realm. Parleremo presto dei suoi limiti, ma una cosa che ho notato è che molti dei reclami fatti al momento del post sono stati risolti da allora.
Quando ho conosciuto Realm, ad esempio, non esisteva ancora il supporto per i metodi personalizzati sui modelli e le chiamate asincrone. All'epoca questi erano rompicapo per molti, ma entrambi sono attualmente supportati.
Tale velocità di sviluppo e reattività ci rende più sicuri che non aspetteremo a lungo funzionalità importanti.
Limitazioni
Come per ogni cosa nella vita, il regno non è tutto rose. Oltre alla limitazione di eredità precedentemente menzionata, ci sono altre carenze da tenere a mente:
Sebbene sia possibile che più thread leggano e scrivano nel database contemporaneamente, gli oggetti Realm non possono essere spostati tra i thread . Quindi, se, ad esempio, recuperi un oggetto realm utilizzando doInBackground
doInBackground()di AsyncTask, che viene eseguito in un thread in background, non puoi passare questa istanza ai metodionPostExecute(), poiché questi vengono eseguiti sul thread principale. Possibili soluzioni alternative per questa situazione sarebbero creare una copia dell'oggetto e passarlo o passare l'id dell'oggetto e recuperare nuovamente l'oggetto suonPostExecute(). Realm offre metodi sincroni e asincroni per la lettura/scrittura.Non c'è supporto per le chiavi primarie di incremento automatico , quindi dovrai gestirne tu stesso la generazione.
Non è possibile accedere al database da processi distinti contemporaneamente . Secondo la loro documentazione, il supporto multiprocesso arriverà presto.
Realm è il futuro delle soluzioni di database mobili
SQLite è un motore di database solido, robusto e collaudato e i database relazionali non scompariranno presto. Ci sono un certo numero di buoni ORM là fuori che funzioneranno anche per molti scenari.
Tuttavia, è importante tenersi aggiornati sulle tendenze attuali.
A questo proposito, penso che Realm sia una delle più grandi tendenze imminenti negli ultimi anni quando si tratta di sviluppo di database mobili.
Realm porta con sé un approccio unico per gestire i dati che è prezioso per gli sviluppatori, non solo perché può essere un'opzione migliore rispetto alle soluzioni esistenti, ma anche perché amplia i nostri orizzonti in termini di nuove possibilità e alza il livello della tecnologia di database mobile.
Hai già esperienza con Realm? Sentiti libero di condividere i tuoi pensieri.
