In this talk I would like to share my experience of using this new mobile database with great potential. We will see which are the major differences compared to SQLite and what makes Realm very fast so as to be called "in realtime". We develop a realtime multiplatform collaborative app starting from scratch, using the latest features introduced by Xamarin Forms to improve the responsiveness. Your viewmodels and bindings for sure will thank you! :-)
6. caratteristiche
non è un ORM costruito su SQLite o simili
non è un Key-Value store (JSON) ma un db a tutti gli
effetti
completamente riscritto, core in C++ (open source)
Swift, Objective-C, Java, JavaScript
.NET con Xamarin (iOS, Android, Mac, UWP, .NET
Framework)
client + server (pensato per essere offline first)
10. zero copy e native links
native objects
ORM traduce in
istruzioni SQL
crea
connessione DB
invia le istruzioni al
disco
esegui la
query
leggi i dati
deserializza e
copia in
formato
intermedio in
memoria
ritorna gli
oggetti finali
copia in memoria in
formato appropriato
per il linguaggio
native objects realm db file
virtual memory
mapped
native links
11. lazy loading
non si può leggere meno di una "pagina"
però le proprietà sono gruppate "in verticale"
le proprietà non utilizzare non vengono lette
si evitano inutili accessi al disco
13. objects
public class Dog : RealmObject
{
[MapTo("name")]
[Required]
public string Name { get; set; }
public int Age { get; set; }
public Person Owner { get; }
}
public class Person : RealmObject
{
[PrimaryKey]
public int Id { get; set; }
public string Name { get; set; }
public IList<Dog> Dogs { get; }
}
to one
to many
inverse
[Backlink(nameof(Dog.Owner))]
public IQueryable<Dog> Dogs { get;
}
14. queries
var artu = realm.Find<Dog>(6);
IQueryable<Dog> puppies = realm.All<Dog>()
.Where(d => d.Age < 2);
var mila = realm.All<Dog>()
.FirstOrDefault(d => d.Name == "Mila");
https://realm.io/docs/dotnet/latest/api/linqsupport.html
15. notifications
realm.RealmChanged += (s, e) =>
{
//do something
}
var subscription = realm.All<Dog>()
.SubscribeForNotifications((sender, changes, error) =>
{
//cosa è stato inserito, elimintato, modificato
});
//later
subscription.Dispose();
var mila = new Dog();
realm.Write(() => realm.Add(mila));
mila.PropertyChanged += (sender, e) =>
{
//do something
}
16. transactions
var realm = Realm.GetInstance(configuration);
realm.Write(() =>
{
var andre = realm.Add(new Person()
{
Id = 6,
Name = "Andrea"
});
var mila = new Dog()
{
Name = "mila",
Age = 1,
}
andre.Dogs.Add(mila);
mila.Owner = andre;
});
21. autenticazione e syncconfiguration
var credentials = Credentials.UsernamePassword("username", "pass");
var authUrl = new Uri("http://realmAuthServerAddress:9080");
var user = await User.LoginAsync(creadentials, authUrl);
var serverUrl = new Uri("realm://realmServerAddress:9080/~/default");
var syncConfig = new SyncConfiguration(user, serverUrl);
var realm = Realm.GetInstance(syncConfig);
var realm = await Realm.GetInstanceAsync(syncConfig);
per chi c'era prima di pranzo la sessione di Antonino e Daniele è stata molto esaustiva nello spiegare Xamarin Forms e quindi ora partiamo da Realm
Xamarin è un framework, ora di Microsoft, che permette di scrivere applicazioni mobile (ma non solo mobile) native utilizzando C# o F# (già di per questo una gran cosa!)
è quindi di fatto un mapping 1:1 con le SDK native (il che significa che è possibile fare esattamente le stesse cose)
per chi non lo sapesse o ne ha sentito solo parlare Xamarin.Forms è un'ulteriore livello di astrazione sopra Xamarin.Android e Xamarin.iOS
Ci mette a disposizione pagine, layout e controlli per poter medellare, scrivendo un codice unico, anche la UI delle nostre app
in questo modo possiamo quindi arrivare ad avere percentuali di code sharing tra le diverse piattaforme che superano il 90%
nasce circa 4 anni fa di Alexander Stigsen (ex Nokia) e Bjarne Christiansen
ha l'obbiettivo di essere veloce, semplice mobile oriented (quindi offline first)
consideriamo che gli utenti di app mobile si aspettano applicazioni che siano
in grado di lavorare offline
reattive
sincronizzate in real time
collaborative
un Realm rappresenta uno storage che può essere su disco o in memoria
non è un ORM costruito su SQLite
ORM sta per object relational mapping, ma al contrario è OBJECTS ALL THE WAY DOWN
gli ORM al contrario sono un'astrazione per il modello relazionale che ci sta sotto: tabelle, recors, chiavi esterne...
un ORM infatti deve continuamente compiere operazioni in backgorund per convertire gli oggeti in qualcosa di intermedio e poi in query sql (converte il grafo di oggetti in query sql)
e ogni volta per risolvere le relazioni tra oggetti deve fare dei JOIN
tutto questo spreca cicli di cpu e costringe ad svolgere operazioni pesanti in thread in background
Realm E' il grafo
il modello relazionale è di fatto molto lontano dalle esigenze (sia di performance che di semplicità) delle moderne app
questo è anche il motivo per cui è stato completamente rescritto a partire dal core, in C++
bindings con le SDK native per tutti i più diffusi linguaggi e in espansione continua
possibilità di avere una parte server per la sincronizzazione
supporta le transazioni ACIDE come ogni buon database
Realm è realmente un db embedded ma con una maniera totalmente diversa di pensare agli oggetti e ai dati
è possibile creare oggetti nel Realm in modo del tutto analogo alla crazione di istanze di normali oggetti
con la possibilità ovvia di settarne le realtive proprietà
Realm racchiude anche in se il concetto di CONNESSIONE a db
è un oggetto a tutti gli effetti e una volta che si ha un'istanza si è già automaticamente connessi
tutti gli oggetti contenuti in un realm sono degli accessor, ossia puntatori ad aree di memoria
questo significa che quando andiamo a leggere un realm object stiamo leggendo direttamente quello che è contenuto su disco
in formato raw
non c'è alcuna copia in memoria perchè tutto il possible è già memory mapped di default (non bisogna quindi deserializzare nulla)
e questo permette di saltare diversi step del tradizionale approcio ORM
Perchè in Realm hanno deciso di costruire un nuovo db partendo da zero?
se guardiamo l'evoluzione dei db negli ultimi 20 anni si può vedere che dal 2007 in poi c'è stata una vera e propria esplosione nello sviluppo di db lato server
alcuni in-memory come Redis, alcuni documentali no-sql come Raven e Mongo...alcuni per Big-Data come Cassandra
ma davvero esiste un db che si chiama Voldemort??!!
ma lato mobile si era rimasti ancora al caro e vecchio SQLite oppure implementazioni simili costruite al di sopra (Core Data, Couchbase, Akavache...)
nessuna novità a livello core...capite che forse qualcosa non va...
l'obbiettivo è quindi quello di avere la stessa flessibilità di un ORM ma con performance molto superiori
una di questa novità è l'algoritmo MVCC (che poi tanto novità non è poichè di fatto è usato ad esempio da Git)
nei database è necessario risolvere problemi di concorrenza
capita infatti che intanto che qualcuno legge ci possono essere operazioni di scrittura concorrenti
vogliamo che le transazioni a db siano ACIDE (atomiche, consistenti, isolate, durevoli)
ci sono diversi modi per risolvere il problema, il più comune è usare una sorta di lock (le oprazioni di read sono bloccate fino a quando la write non ha finito)
MVCC risolve quasto provlema, ogni thread ha un suo snapshot consistente del db ad un certo momento nel tempo (quasi come se fosse un oggetto immutabile)
è lo stesso concetto che sta alla base di Git
posso lavorare su differenti branch, ossia in questo caso differenti verisoni del db, senza dover avere una copia completa di tutto il db
la differenza è che però in Realm solo una singola operazione di write può avvenire in un certo momento e lavora sempre sulla versione precedente più aggiornata
un realm è un B-tree in cui vi è sempre un nodo radice Realm.getR().getB().getE()
nel momento in cui vie una write l'albero viene forkato e le modfiche vengono applicate ai nodi interessati
in questo modo se qualcosa va storto durante l'operazione comunque la versione precedente è intatta
nel frattempo tutti gli altri posso continuare a leggere la versione precedente (write non blocca le read)
a questo punto Realm usa two-phase commit per verificare che le modifiche siano state effettivamente scritte su disco
e adesso può muovere il puntatore alla nuova root notificando tutti gli altri che questa è la nuova versione
gli altri thread ricevono automaticamente il refresh alla nuova versione al prossimo giro di run-loop
se il thread è di background occorre esplicitamente chiamare Realm.Refresh()
i vecchi nodi vengono dati in pasto al garbage collector
se pensiamo a come lavora un ORM più o meno questi sono gli step...
ogni Realm object parla direttamente al file sottostante tramite puntatori
nessuna copia da una locazione di memoria ad un' altra realm persiste gli oggetti direttamente OBJECTS ALL THE WAY DOWN
non c'è qundi bisogno di trasformare tra diverse rappresentazioni in memoria degli oggetti, riallineare bit, serializzare/deserializzare
questo è il motivo per cui si, si possono fare query utilizzando UI thread senza problemi
se Realm è un gigantesco albero allora ci saranno link da tutte le parti,
LINK NATIVI fino a livello raw di file system
gli oggetti referenziati sono cittadini di prima classe e per calcolare relazioni anche complesse è sufficente seguire puntatori piuttosto che fare costosi join
seguire un link ad un value type semplice, un intero ad esempio, non è più costoso che risolvere una relazione, sempre un link è
Realm evita tutti i processi di copia e mappatura da un formato all'altro perchè il file del db è direttamente mappato in memoria
e NON C'E' differenza di formato tra disco e memoria vera
Realm è quindi in grado di calcolare e accedere a qualsiasi offset nel file come se fosse già in memoria anche se questa è virtuale
allo stesso modo le realzioni tra oggetti sono estremamente veloci perchè di fatto sono solo puntatori ad altre locazioni di memoria risolvibili tramite un cammino sull'albero
per come sono fatti a livello hardware i dischi (anche SSD) non è possibile leggere un singolo bit
si legge sempre la pagina, un gruppo contiguo di byte
tuttavia possiamo ripensare a come le prop sono allocate in memoria
la maggior parte dei db stora i dati un una sorta di "livello orizzontale"
in SQLite ad esempio se voglio leggere anche solo una prop devo tirare su l'intera riga e questo perchè sono storate vicine
Realm la contrario tenta di storare le prop anche di un singolo oggetto in una struttura verticale linkata
questo permette di non tirare su l'intero oggetto se servono solo alcune sue proprietà
e si evitano inutili round trip al disco
Ricordare che Realm può anche gestire built-in Encryption AES256 + SHA2 a basso livello facilitata dal fatto che è tutto mappato in memoria virtuale
è quindi sufficiente sollevare un'eccezione di violazione di memoria nel caso
In Realm ci sono 4 concetti fondamentali...
partiamo da eventi UI triggerati dall'utente o da qualche servizio esterno
attraverso una transazione degli oggetti vengono modificati (as esempio arriva un nuovo valore da sincronizzazione...)
gli oggetti a db sono magari legati a delle query per comporre viste (ad esempio una ListView in Forms)
queste query sono "live" e vengono automaticamente notificate di eventuali cambiamenti che sono stati fatti agli oggetti
questo significa che i binding si aggiornano in automatico
è inoltre possibile "ascoltare" le notifiche di cambiamento e reagire di conseguenza per fare cose...
e il giro ricomincia...
supporta quasi tutti i value types di C# e i corrispettivi nullable
gli oggetti ereditano da RealmObject in maniera diretta (altirmenti lo weaver fallisce a compile time)
se quindi si vogliono avere propietà comuni occorre utilizzare interfacce
a compile time infatti tutti i RealmObject vengono processati da Fody che fa inject di codice
spiegare brevemente che cosa è fody:
è un tool per fare weaving (tessere) del codice .NET
è possibile manipolare l'IL e quindi fare injection di codice a compile time
tutte le prop standard che hanno getter e setter automatici vengono processate dallo weaver e persistite se non diversamente specificato
è possibile specificare una chiave primaria, in questo modo sarà poi possibile fare l'update di un realm object passando il flag true al metodo Add (upsert)
è inoltre possibile utilizzare il metodo Find
è possibile mappare i nomi delle prop se li si vuole in diverso case a db
è possibile specificare se una prop è necessaria o meno
se si specifica un costruttore con parametri è necessario anche specificare quello senza parametri
è possibile specificare i backlink e usare il metodo GetBackLinks(string objectType, string property) di un un realm object per ottenere tutte le istanze di altri oggetti
che sono legate all'oggetto in questione attraverso una loro prop specifica
nel caso dei backlinks la collection ( Dogs ) è conforme a IQueryable e questo significa che non si può fare Add (non c'è il metodo)
questo perchè la collection viene autoamticamente popolata quando la rispettiva proprietà di backlink viene assegnata –> person.Owner
i backlink sono meno onerosi e di più facile mantenimento rispetto alla doppi relazione padre-figlio
se per l'oggetto è specificata la primary key posso utilizzare il metodo Find (la pk automaticamente crea un indice)
è possibile utilizzare LINQ partendo da realm.All<T> e applicando poi gli altri operatori
il tipo di ritorno è IQueryable il che significa lazy e posso aggiungere condizioni alla mia query a step successivi
fino a quando no utilizzo il ToList() la query non viene eseguita ettenzione che però gli oggetti non vengono copiati!!
il risultato è una lista di reference agli oggetti che matchano e si lavora direttamente con gli oggetti originali
se non faccio il ToList() posso decidere di iterare sugli oggetti e solo a questo punto, quando accedo all'oggetto per leggere una sua prop la query viene eseguita
true lazy loading significa che anche a livello di singolo oggetto non lo tiro su completamente se devo magari accedere ad una sola prop
è possibile ascoltare cambiamenti di un intero Realm
di un singolo oggetto (dopo che è stato aggiunto al Realm comincerà a notificare)
nel caso si vogliano ascoltare i cambiamenti relativi ad una particoalre istanza è possibile registrarsi sia prima che dopo che questa è stata aggiunta al Realm
o di una collection
quindi ad esempio possiamo avere un background thread che aggiunge o modifica dati ad una collection e ascoltare i cambiamenti
questo ovviamente permette i binding con Forms
per quanto riguarda le collection di fatto IQueryable è "live" e viene sempre aggiornata
ogni volta che iteriamo sugli oggetti ritornati questo set è aggiornato
allo stesso modo IList rappresenta relazioni to-many che sono anche esse "live"
di fatto per entrambe viene implementata l'interfaccia IRealmCollection<T> a cui è possibile sottoscriversi per ricevere notifiche
a sua volta questa implementa IReadOnlyList<T> i INotifyCollectionChanged
le notifiche possono anche essere multi processo, MVCC permette questo di default e la comunicazione avviene tramite NAMED PIPE
appena un'istanza di un RealmObject viene aggiunta al Realm diventa Managed
da questo punto è un oggetto vivo che si aggiorna automaticamente ogni volta che qualcosa cambia
infatti abbiamo di fatto creato un accessor ai byte che compongono il nostro oggetto
se viene modificata una proprietà il cambiamento viene riflesso in tutte le istanze di altri oggetti che puntano a questa
(l'oggetto è uno solo...gli altri sono tutti link)
questo aspetto rende Realm molto veloce ed efficiente
se un controllo UI mostra dati relativi ad un RealmObject non sarà necessario aggiornare manualmente la UI
i binding risultano decisamente più semplici e il codice più ordinato
tutte le modifiche ad un oggetto devono essere fatte all'interno di una transazione
è preferibile fare una sola transazione più grande piuttosto che tante piccole
l'operazione di write è molto veloce ma cmq bloccante e quindi se onerosa è meglio utilizzare la rispettiva WriteAsync()
l'operazioni di write sono bloccanti per le altre write ma non per le read MVCC (multi version concurrent control)
realm.GetInstance() ritorna la stessa istanza se chiamata dallo stesso thread e con la stessa configurazione
ogni realm instance è per-thread e per-configurazione
ok tutto bello ma la sincronizzazione tra web service e db locale devo sempre farmela a mano tramite servizi REST??
non c'è solo il db cliente che cmq può essere utilizzato anche da solo rimpiazzando completamente SQLite
esiste anche ROS, Realm Object Server...
ROS può essere hostato su OSX o Linux, è in arrivo anche per WIndows
Inoltre per iOS e Android è in preview Realm Cloud che permette di avere ROS in cloud
la SDK per .NET dovrebbe essere disponile la prossima settimana
esistono vari adapter per "parlare" con i diversi db lato server
e possibile parlare e modificare direttamente un Realm in ROS attraverso RealmStudio
oppure attraverso diverse SDK tra cui anche nodeJS .NET Core
oppure attraverso un normale servizio REST
ROS sincronizza in realm time tutti i Realm collegati sui vari device
esattamente come sui singoli db locali è possibile ascoltare i cambiamenti avvenuti i un Realm e agire di conseguenza
ad esempio posso chiamare una Azure Function oppure utilizzare i Cognitive Services per fare qualcosa e riaggiarnare poi il mio Real condiviso
un importante concetto per aver un Realm condiviso è quello di utente...
per l'utente è possibile utilizzare diversi metodi di autenticazione (anche esterni tra cui AD, Google...)
oppure anche semplicemente tramite utente e password (in questo caso è meglio utilizzare HTTPS)
a questo punto occorre chiamare l'endpoint di auth
e dopo costruire una nuova configurazione
da utilizzare per avere l'istanza sincronizzata di realm