ServiceStack.Net Redis: memorizzazione di oggetti correlati e ID object correlati

Il mio team ha deciso di collaborare con Redis tramite il client Redis ServiceStack.net come repository sottostante per un nuovo sito web ad alto volume su cui stiamo lavorando. Non sono sicuro di dove cercare la documentazione per questa domanda (sia per i documenti Redis generici o specifici documenti ServiceStack.Net o entrambi) – esiste in realtà una fonte definitiva per la documentazione su come implementare un Redis tramite ServiceStack.Net che include tutto quello che devi sapere sui concetti di Redis e sui concetti di ServiceStack.Net, o dobbiamo integrare la documentazione da entrambi gli aspetti separatamente per ottenere l’immagine completa ?.

Sono solo alle prese con quanto esattamente per memorizzare gli oggetti correlati nel grafico degli oggetti del nostro modello. Ecco uno scenario semplice con cui voglio lavorare:

Ci sono due oggetti nel sistema: User e Feed . Nei termini RDBMS questi due oggetti hanno una relazione uno-a-molti, cioè un User ha una raccolta di oggetti Feed e un feed può appartenere solo a un User . I feed saranno sempre accessibili da Redis tramite il loro utente, ma occasionalmente vorremmo ottenere l’accesso all’utente tramite un’istanza di feed.

Quindi la domanda che ho è se dovremmo memorizzare gli oggetti correlati come proprietà o dovremmo memorizzare i valori Id degli oggetti correlati? Illustrare:

Approccio A :

 public class User { public User() { Feeds = new List(); } public int Id { get; set; } public List Feeds { get; set; } // Other properties } public class Feed { public long Id { get; set; } public User User { get; set; } } 

Approccio B :

 public class User { public User() { FeedIds = new List(); } public long Id { get; set; } public List FeedIds { get; set; } public List GetFeeds() { return repository.GetFeeds( FeedIds ); } } public class Feed { public long Id { get; set; } public long UserId { get; set; } public User GetUser() { return repository.GetUser( UserId ); } } 

Quale degli approcci sopra funzionerà meglio? Ho visto entrambi gli approcci usati in vari esempi, ma ho l’impressione che alcuni degli esempi che ho visto potrebbero non essere la migliore pratica.

Alcune semplici domande correlate:

  • Se apporto una modifica a un object, questo verrà automaticamente riflesso in Redis o richiederà un salvataggio? Sto assumendo quest’ultimo, ma devo essere assolutamente chiaro.
  • Se io (posso) usare l’Approccio A, un aggiornamento all’object Utente X si rifletterà sull’intero grafico dell’object ovunque venga fatto riferimento o sarà necessario salvare le modifiche attraverso il grafico?
  • C’è un problema con la memorizzazione di un object tramite la sua interfaccia (ad esempio, usare IList invece di List ?

Scusate se queste domande sono un po ‘di base – fino a 2 settimane fa non avevo mai sentito parlare di Redis – per non parlare di ServiceStack – (e nemmeno di qualcuno nel mio team) quindi stiamo davvero iniziando da zero qui …

Piuttosto che rifare molta altra documentazione che è là fuori in the wild, ne elencherò un paio in giro per alcune informazioni di base su Redis + Client Redis di ServiceStack:

  • Cosa pensare quando si progetta un’applicazione Redis NoSQL
  • Progettare un database NoSQL usando Redis
  • Panoramica generale di Redis e .NET
  • Controllo delle versioni di Schemaless e migrazione dei dati con C # Redis Client

Non c’è magia – Redis è una canvas bianca

Innanzitutto voglio sottolineare che l’utilizzo di Redis come archivio dati fornisce solo una canvas bianca e non ha alcun concetto di entity framework correlate da solo. cioè fornisce solo l’accesso a strutture dati comp-sci distribuite. Il modo in cui le relazioni vengono memorizzate dipende in ultima analisi dal driver client (es. ServiceStack C # Redis Client) o dallo sviluppatore dell’app, utilizzando le primitive operazioni sulla struttura dei dati di Redis. Dal momento che tutte le principali strutture dati sono implementate in Redis, in pratica hai completa libertà su come vuoi strutturare e archiviare i tuoi dati.

Pensa a come strutturerai le relazioni nel codice

Quindi il modo migliore per pensare a come archiviare contenuti in Redis è ignorare completamente il modo in cui i dati vengono archiviati in una tabella RDBMS e pensare a come è memorizzato nel codice, cioè utilizzando le classi di raccolta C # incorporate in memoria – quale Redis esegue il mirroring in comportamento con le loro strutture dati lato server.

Nonostante non abbiano un concetto di quadro correlate, le strutture di dati predefinite di Set e SortedSet di Redis rappresentano il modo ideale per memorizzare gli indici. Ad esempio, la collezione Set di Redis memorizza solo un massimo di 1 occorrenza di un elemento. Ciò significa che puoi tranquillamente aggiungere elementi / chiavi / id ad esso e non preoccuparti se l’elemento esiste già in quanto il risultato finale sarà lo stesso se lo avessi chiamato 1 o 100 volte – cioè è idempotente, e in definitiva solo 1 elemento rimane memorizzato in il set. Quindi, un caso d’uso comune è quando si memorizza un object grafico (radice aggregata) per memorizzare gli ID delle entity framework figlio (ovvero le chiavi esterne) in un Set ogni volta che si salva il modello.

Visualizzare i tuoi dati

Per una buona visualizzazione del modo in cui le quadro sono memorizzate in Redis, consiglio di installare l’ interfaccia utente di Redis Admin che funziona bene con il client C # Redis di ServiceStack poiché utilizza la convenzione di denominazione delle chiavi di seguito per fornire una bella vista gerarchica, raggruppando le entity framework digitate insieme (nonostante tutte le chiavi esistenti nello stesso spazio delle chiavi globale).

Per visualizzare e modificare un’entity framework, fare clic sul collegamento Modifica per vedere e modificare la rappresentazione JSON interna dell’ quadro selezionata. Spero che tu possa prendere decisioni migliori su come progettare i tuoi modelli una volta che puoi vedere come sono memorizzati.

Come vengono memorizzate le POCO / quadro

Il client C # Redis funziona con qualsiasi POCO che abbia una singola chiave primaria – che per impostazione predefinita è prevista per essere Id (sebbene questa convenzione sia sostituibile con ModelConfig ). Essenzialmente i POCO vengono archiviati in Redis come JSON serializzati con sia il typeof(Poco).Name sia l’ Id usato per formare una chiave univoca per typeof(Poco).Name . Per esempio:

 urn:Poco:{Id} => '{"Id":1,"Foo":"Bar"}' 

I POCO nel client C # sono serializzati convenzionalmente con Serializer Json veloce di ServiceStack, in cui solo le proprietà con getter pubblici vengono serializzate (e i setter pubblici per ottenere la mancata serializzazione ).

I valori predefiniti sono sovrascrivibili con [DataMember] attrs, ma non sono raccomandati in quanto ottimizzano i tuoi POCO.

Le entity framework sono bloccate

Quindi, sapendo che i POCO in Redis sono semplicemente distrutti, si desidera mantenere solo i dati radice non aggregati sui POCO come proprietà pubbliche (a meno che non si desideri memorizzare intenzionalmente dati ridondanti). Una buona convenzione consiste nell’usare metodi per recuperare i dati correlati (dato che non verranno serializzati) ma dice anche all’app quali metodi effettuano chiamate remote per leggere i dati.

Quindi la domanda se il Feed deve essere memorizzato con l’ Utente è se siano o meno dati radice non aggregati, cioè se vuoi o meno accedere ai feed degli utenti al di fuori del contesto dell’utente? Se no, lascia la proprietà List Feeds sul tipo User .

Mantenere gli indici personalizzati

Se tuttavia si desidera mantenere accessibili tutti i feed in modo indipendente, ovvero con redisFeeds.GetById(1) è necessario archiviarli all’esterno dell’utente e mantenere un indice che colleghi le 2 quadro.

Come hai notato, ci sono molti modi per memorizzare le relazioni tra entity framework e il modo in cui lo fai è in gran parte una questione di preferenza. Per l’entity framework figlio in una relazione genitore> figlio , si vorrebbe sempre memorizzare il ParentId con l’entity framework figlio. Per il genitore puoi scegliere di archiviare una collezione di ChildIds con il modello e quindi eseguire un singolo recupero per tutte le quadro child per reidratare il modello.

Un altro modo è di mantenere l’indice al di fuori del padre dto nel proprio Set per ogni istanza genitore. Alcuni buoni esempi sono nel codice sorgente C # della demo di Redis StackOverflow in cui è archiviata la relazione Users > Questions e Users > Answers :

 idx:user>q:{UserId} => [{QuestionId1},{QuestionId2},etc] idx:user>a:{UserId} => [{AnswerId1},{AnswerId2},etc] 

Sebbene C # RedisClient includa il supporto per una convenzione padre / figlio predefinita tramite le sue API TParent.StoreRelatedEntities () , TParent.GetRelatedEntities() e TParent.DeleteRelatedEntities() cui viene mantenuto un indice dietro la scena che assomiglia a:

 ref:Question/Answer:{QuestionId} => [{answerIds},..] 

In effetti queste sono solo alcune delle tue possibili opzioni, in cui ci sono molti modi diversi per raggiungere lo stesso fine e in cui hai anche la libertà di eseguire il rollover.

Le libertà di NoSQL senza schemi e libertà di digitazione dovrebbero essere accettate e non dovresti preoccuparti di cercare di seguire una struttura rigida e predefinita che potresti avere familiarità con l’uso di un RDBMS.

In conclusione, non esiste un vero e proprio modo per archiviare i dati in Redis, ad es. C # Redis Client fa alcune ipotesi per fornire un’API di alto livello intorno ai POCO e blocca i POCO nei valori di stringa di Redis sicuri per le stringhe – sebbene ce ne siano altri i clienti preferiranno invece memorizzare le proprietà di entity framework in Redis Hashes (Dizionari). Entrambi funzioneranno.