ServiceStack.Net Redis: хранение связанных объектов и идентификаторов связанных объектов
Моя команда решила работать с Redis через ServiceStack.net Redis Client в качестве основного репозитория для нового большого сайта, над которым мы работаем. Я не совсем уверен, где искать документацию по этому вопросу (либо для общих документов Redis, либо для конкретных документов ServiceStack.Net, либо для обоих) - действительно ли существует точный источник документации о том, как реализовать Redis через ServiceStack.Net, которая включает все, что вам нужно знать как о концепциях Redis, так и о концепциях ServiceStack.Net, или нам нужно объединить документацию по обоим аспектам отдельно, чтобы получить полную картину?
Я просто пытаюсь понять, как именно хранить связанные объекты в графе объектов нашей модели. Вот простой сценарий, с которым я хочу работать:
В системе есть два объекта: User
а также Feed
, В терминах RDBMS эти два объекта имеют отношение один ко многим, то есть User
имеет коллекцию Feed
объекты и канал могут принадлежать только одному User
, Ленты всегда будут доступны из Redis через их пользователя, но иногда мы хотим получить доступ к пользователю через экземпляр фида.
Таким образом, у меня есть вопрос, должны ли мы хранить связанные объекты как свойства или мы должны хранить Id
значения связанных объектов? Проиллюстрировать:
Подход А:
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; }
}
Подход Б:
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 );
}
}
Какой из вышеперечисленных подходов будет работать лучше? Я видел оба подхода, использованных в различных примерах, но у меня сложилось впечатление, что некоторые из примеров, которые я видел, могут быть не лучшими.
Несколько простых связанных вопросов:
- Если я внесу изменение в объект, оно будет автоматически отражено в Redis или потребует сохранения? Я предполагаю последнее, но должно быть абсолютно ясно.
- Если я (смогу) использовать подход A, будет ли отражено обновление пользовательского объекта X по всему графу объектов, где бы на него ни ссылались, или будет необходимо сохранить изменения по всему графу?
- Есть ли проблема с сохранением объекта через его интерфейс (т.е. использовать
IList<Feed>
в отличие отList<Feed>
?
Извините, если эти вопросы немного просты - до тех пор, пока 2 недели назад я даже не слышал о Redis - не говоря уже о ServiceStack - (и никого не было в моей команде), поэтому мы действительно начинаем здесь с нуля...
1 ответ
Вместо того, чтобы перефразировать много другой документации, которая существует в дикой природе, я перечислю пару примеров для получения справочной информации о Redis + клиенте Redis + ServiceStack:
- О чем нужно думать при разработке приложения NoSQL Redis
- Проектирование базы данных NoSQL с использованием Redis
- Общий обзор Redis и.NET
- Версии без схемы и перенос данных с помощью клиента C# Redis
Нет магии - Redis - это чистый холст
Во-первых, я хочу отметить, что использование Redis в качестве хранилища данных просто обеспечивает пустой холст и само по себе не имеет понятия связанных сущностей. то есть он просто обеспечивает доступ к распределенным структурам данных comp-sci. То, как хранятся отношения, в конечном итоге зависит от драйвера клиента (т.е. ServiceStack C# Redis Client) или разработчика приложения, используя примитивные операции структуры данных Redis. Поскольку все основные структуры данных реализованы в Redis, вы практически свободны в том, как вы хотите структурировать и хранить ваши данные.
Подумайте, как бы вы структурировали отношения в коде
Таким образом, лучший способ подумать о том, как хранить вещи в Redis, - это полностью игнорировать то, как данные хранятся в таблице RDBMS, и думать о том, как они хранятся в вашем коде, то есть с использованием встроенных классов коллекции C# в памяти - который Redis отражает поведение своих серверных структур данных.
Несмотря на отсутствие концепции связанных сущностей, встроенные в Redis структуры данных Set и SortedSet обеспечивают идеальный способ хранения индексов. Например, коллекция Set Redis хранит не более 1 экземпляра элемента. Это означает, что вы можете безопасно добавлять элементы / ключи / идентификаторы к нему и не беспокоиться о том, что элемент уже существует, так как конечный результат будет таким же, если бы вы назвали его 1 или 100 раз - то есть он идемпотентен, и в конечном итоге в нем остается только 1 элемент. набор. Таким образом, наиболее распространенный вариант использования при хранении графа объектов (агрегатного корня) - сохранение идентификаторов дочерних объектов (внешних ключей) в наборе при каждом сохранении модели.
Визуализация ваших данных
Для хорошей наглядности того, как объекты хранятся в Redis, я рекомендую установить пользовательский интерфейс администратора Redis, который хорошо работает с клиентом ServiceStack C# Redis, так как он использует приведенное ниже соглашение об именах ключей для обеспечения хорошего иерархического представления, объединяя ваши типизированные объекты вместе (несмотря на все ключи существующий в том же глобальном пространстве ключей).
Чтобы просмотреть и отредактировать объект, нажмите на ссылку " Изменить", чтобы просмотреть и изменить внутреннее представление JSON выбранного объекта. Надеемся, что вы сможете принимать более правильные решения о том, как проектировать свои модели, как только увидите, как они хранятся.
Как хранятся POCO / сущности
Клиент C# Redis работает с любыми POCO, которые имеют один первичный ключ - который по умолчанию ожидается Id
(хотя это соглашение переопределено с ModelConfig). По сути, POCO хранятся в Redis как сериализованный JSON с обоими typeof(Poco).Name
и Id
используется для формирования уникального ключа для этого экземпляра. Например:
urn:Poco:{Id} => '{"Id":1,"Foo":"Bar"}'
POCO в клиенте C# традиционно сериализуются с использованием быстрого сериализатора Json ServiceStack, где сериализуются только свойства с общедоступными получателями (и общедоступные установщики, чтобы возвращать десериализацию обратно).
Значения по умолчанию могут быть переопределены с [DataMember]
attrs, но не рекомендуется, так как это ухудшает ваши POCO.
Сущности blobbed
Таким образом, зная, что POCO в Redis просто являются BLOB-объектами, вы хотите сохранить неагрегированные корневые данные в ваших POCO в качестве общедоступных свойств (если только вы не намеренно хотите хранить избыточные данные). Хорошее соглашение состоит в том, чтобы использовать методы для извлечения связанных данных (поскольку они не будут сериализованы), но также сообщает вашему приложению, какие методы делают удаленные вызовы для чтения данных.
Таким образом, вопрос о том, должен ли Фид храниться вместе с Пользователем, заключается в том, являются ли это неагрегированными корневыми данными, т.е. хотите ли вы получать доступ к фидам пользователей вне контекста пользователя? Если нет, то оставьте List<Feed> Feeds
собственность на User
тип.
Ведение пользовательских индексов
Однако, если вы хотите, чтобы все каналы были доступны независимо, т.е. redisFeeds.GetById(1)
тогда вы захотите сохранить его за пределами пользователя и поддерживать индекс, связывающий 2 объекта.
Как вы заметили, есть много способов сохранить отношения между сущностями, и то, как вы это делаете, во многом зависит от предпочтений. Для дочерней сущности в родительских> дочерних отношениях вы всегда хотели бы сохранить ParentId с дочерней сущностью. Для Родителя вы можете либо сохранить коллекцию ChildIds вместе с моделью, а затем сделать одну выборку для всех дочерних объектов для повторного увлажнения модели.
Другой способ - сохранить индекс вне родительского dto в своем собственном наборе для каждого родительского экземпляра. Некоторые хорошие примеры этого есть в исходном коде C# демонстрации Redis Stackru, где взаимосвязь Users > Questions
а также Users > Answers
хранится в:
idx:user>q:{UserId} => [{QuestionId1},{QuestionId2},etc]
idx:user>a:{UserId} => [{AnswerId1},{AnswerId2},etc]
Хотя C# RedisClient действительно поддерживает стандартное родительское / дочернее соглашение через TParent.StoreRelatedEntities (), TParent.GetRelatedEntities<TChild>()
а также TParent.DeleteRelatedEntities()
API, где за сценой поддерживается индекс, который выглядит следующим образом:
ref:Question/Answer:{QuestionId} => [{answerIds},..]
По сути, это лишь некоторые из ваших возможных вариантов, когда есть много разных способов достижения одной и той же цели, и у вас также есть свобода выбора.
Свободные от произвольной типизации свободы NoSQL должны быть приняты, и вам не следует беспокоиться о попытке следовать жесткой, заранее определенной структуре, с которой вы, возможно, знакомы при использовании СУБД.
В заключение, нет никакого реального правильного способа хранения данных в Redis, например, клиент C# Redis делает некоторые предположения для обеспечения высокоуровневого API вокруг POCO, и он блокирует POCO в двоичных безопасных строковых значениях Redis - хотя есть и другие вместо этого клиенты предпочтут хранить свойства сущностей в Redis Hashes (Словари). Оба будут работать.