c# - ServiceStack.Net Redis: Almacenamiento de objetos relacionados vs. Id de objeto relacionado
(1)
En lugar de volver a codificar una gran cantidad de otra documentación que se encuentra en la naturaleza, voy a enumerar un par de personas para obtener información de contexto sobre Redis + ServiceStack''s Redis Client:
- Qué pensar al diseñar una aplicación NoSQL Redis
- Diseñando una base de datos NoSQL usando Redis
- Descripción general de Redis y .NET
- Versiones de Schemaless y migraciones de datos con C # Redis Client
No hay magia - Redis es un lienzo en blanco
Primero quiero señalar que usar Redis como un almacén de datos solo proporciona un lienzo en blanco y no tiene ningún concepto de entidades relacionadas por sí mismo. es decir, simplemente proporciona acceso a estructuras de datos comp-sci distribuidas. La forma en que se almacenan las relaciones depende en última instancia del controlador del cliente (es decir, ServiceStack C # Redis Client) o del desarrollador de la aplicación, mediante el uso de operaciones de estructura de datos primitivas de Redis. Dado que todas las principales estructuras de datos se implementan en Redis, básicamente tiene completa libertad sobre cómo desea estructurar y almacenar sus datos.
Piensa cómo podrías estructurar las relaciones en el código
Entonces, la mejor manera de pensar cómo almacenar cosas en Redis es ignorar por completo cómo se almacenan los datos en una tabla RDBMS y pensar cómo se almacena en el código, es decir, utilizando las clases de colección C # incorporadas en la memoria. que Redis refleja en comportamiento con sus estructuras de datos del lado del servidor.
A pesar de no tener un concepto de entidades relacionadas, las estructuras de datos Set y SortedSet integradas de Redis proporcionan la forma ideal de almacenar índices. Por ejemplo, la colección Set de Redis solo almacena un máximo de 1 ocurrencia de un elemento. Esto significa que puede agregar elementos / claves / identificadores de manera segura y no importa si el artículo ya existe, ya que el resultado final será el mismo si lo hubiera llamado 1 o 100 veces, es decir, es idempotente y, en última instancia, solo 1 elemento permanece almacenado en el conjunto. Por lo tanto, un caso de uso común es cuando se almacena un gráfico de objetos (raíz agregada) para almacenar los Id. De la entidad secundaria (también conocidos como claves externas) en un conjunto cada vez que se guarda el modelo.
Visualizando tus datos
Para una buena visualización de cómo se almacenan las entidades en Redis, recomiendo instalar la interfaz de usuario de Redis Admin que funciona bien con C # Redis Client de ServiceStack ya que utiliza la convención de nomenclatura a continuación para proporcionar una buena vista jerárquica, agrupando las entidades escritas (a pesar de todas las claves existente en el mismo espacio de claves global).
Para ver y editar una entidad, haga clic en el enlace Editar para ver y modificar la representación JSON interna de la entidad seleccionada. Con suerte, podrá tomar mejores decisiones sobre cómo diseñar sus modelos una vez que pueda ver cómo se almacenan.
Cómo se almacenan POCO / Entidades
C # Redis Client funciona con cualquier POCO que tenga una sola clave primaria, que de manera predeterminada se espera que sea Id
(aunque esta convención es reemplazable con ModelConfig ). Esencialmente, los POCO se almacenan en Redis como JSON serializado con el tipo de typeof(Poco).Name
y el Id
utilizados para formar una clave única para esa instancia. P.ej:
urn:Poco:{Id} => ''{"Id":1,"Foo":"Bar"}''
Las POCO en el cliente C # se serializan convencionalmente utilizando el rápido Serializador Json de ServiceStack, donde solo las propiedades con captadores públicos se serializan (y los organismos públicos se vuelven a serializar).
Los valores predeterminados son [DataMember]
con [DataMember]
pero no se recomiendan, ya que uglifies sus POCO.
Las entidades están blobbed
Por lo tanto, sabiendo que las POCO en Redis simplemente están bloqueadas, solo desea mantener datos de raíz no agregados en sus POCO como propiedades públicas (a menos que desee almacenar datos redundantes deliberadamente). Una buena convención es usar métodos para recuperar los datos relacionados (ya que no se serializarán), pero también le dice a su aplicación qué métodos hacen llamadas remotas para leer datos.
Entonces, la pregunta sobre si el Feed debería almacenarse con el Usuario es si se trata de datos de root no agregados, es decir, si desea o no acceder a los feeds de los usuarios fuera del contexto del usuario. Si no, deje la propiedad List<Feed> Feeds
en el tipo de User
.
Mantenimiento de índices personalizados
Sin embargo, si desea mantener todos los feeds accesibles de forma independiente, es decir, con redisFeeds.GetById(1)
, querrá almacenarlo fuera del usuario y mantener un índice que vincule las 2 entidades.
Como ha notado, hay muchas maneras de almacenar las relaciones entre las entidades y cómo lo hace es en gran medida una cuestión de preferencia. Para la entidad hijo en una relación padre> hijo , siempre deseará almacenar ParentId con la entidad hijo. Para Parent, puede optar por almacenar una colección de ChildIds con el modelo y luego hacer una única extracción para todas las entidades secundarias para rehidratar el modelo.
Otra forma es mantener el índice fuera del dto padre en su propio Conjunto para cada instancia padre. Algunos buenos ejemplos de esto se encuentran en el código fuente de C # de la demostración de Redis StackOverflow donde la relación de Users > Questions
y Users > Answers
se almacena en:
idx:user>q:{UserId} => [{QuestionId1},{QuestionId2},etc]
idx:user>a:{UserId} => [{AnswerId1},{AnswerId2},etc]
Aunque el C # RedisClient sí incluye compatibilidad para una convención principal / secundaria predeterminada a través de sus TParent.StoreRelatedEntities() , TParent.GetRelatedEntities<TChild>()
y TParent.DeleteRelatedEntities()
donde se mantiene un índice detrás de la escena que se ve así:
ref:Question/Answer:{QuestionId} => [{answerIds},..]
Efectivamente, estas son solo algunas de sus opciones posibles, donde hay muchas maneras diferentes de lograr el mismo fin y en las que también tiene la libertad de hacer suyas.
Deben adoptarse las libertades sin esquema y de tipas sueltas de NoSQL, y no debe preocuparse por tratar de seguir una estructura rígida y predefinida con la que esté familiarizado cuando utilice un SGBDR.
En conclusión, no existe una forma correcta de almacenar datos en Redis, por ejemplo, C # Redis Client hace algunas suposiciones para proporcionar una API de alto nivel alrededor de las POCO y las POCO en las cadenas binarias seguras de Redis, aunque hay otras los clientes preferirán almacenar las propiedades de una entidad en Redis Hashes (Diccionarios) en su lugar. Ambos funcionarán.
Mi equipo ha decidido trabajar con Redis a través de ServiceStack.net Redis Client como repositorio subyacente de un nuevo sitio web de alto volumen en el que estamos trabajando. No estoy seguro de dónde buscar documentación para esta pregunta (ya sea para documentos generales de Redis o documentos específicos de ServiceStack.Net o ambos): ¿existe realmente una fuente definitiva de documentación sobre cómo implementar un Redis a través de ServiceStack.Net que incluya todo lo que necesita saber sobre los conceptos de Redis y los conceptos de ServiceStack.Net, o ¿necesitamos integrar la documentación de ambos aspectos por separado para obtener una imagen completa ?.
Solo estoy lidiando con la forma exacta de almacenar objetos relacionados en el gráfico de objetos de nuestro modelo. Aquí hay un escenario simple con el que quiero trabajar:
Hay dos objetos en el sistema: User
y Feed
. En términos RDBMS, estos dos objetos tienen una relación de uno a varios, es decir, un User
tiene una colección de objetos de Feed
y un feed solo puede pertenecer a un User
. Siempre se accederá a los feeds desde Redis a través de su usuario, pero ocasionalmente querremos obtener acceso al usuario a través de una instancia de feed.
Entonces, la pregunta que tengo es si deberíamos almacenar los objetos relacionados como propiedades o deberíamos almacenar los valores Id
de los objetos relacionados. Para ilustrar:
Enfoque A :
public class User
{
public User()
{
Feeds = new List<Feed>();
}
public int Id { get; set; }
public List<Feed> Feeds { get; set; }
// Other properties
}
public class Feed
{
public long Id { get; set; }
public User User { get; set; }
}
Enfoque B :
public class User
{
public User()
{
FeedIds = new List<long>();
}
public long Id { get; set; }
public List<long> FeedIds { get; set; }
public List<Feed> 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 );
}
}
¿Cuál de los enfoques anteriores funcionará mejor? He visto ambos enfoques utilizados en varios ejemplos, pero me da la impresión de que algunos de los ejemplos que he visto pueden no ser los mejores.
Algunas preguntas simples relacionadas:
- Si realizo un cambio en un objeto, ¿se reflejará automáticamente en Redis o requerirá un guardado? Estoy asumiendo lo último, pero necesito ser absolutamente claro.
- Si yo (puedo) usar el Enfoque A, ¿se reflejará una actualización del Objeto del Usuario X en todo el gráfico del objeto donde sea que se haga referencia o será necesario guardar los cambios en el gráfico?
- ¿Hay algún problema con el almacenamiento de un objeto a través de su interfaz (es decir, use
IList<Feed>
en lugar deList<Feed>
?
Perdón, si estas preguntas son un poco básicas, hasta hace dos semanas ni siquiera había oído hablar de Redis, menos aún de ServiceStack, (ni tenía a nadie en mi equipo), así que realmente estamos empezando desde cero aquí ...