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:

Нет магии - 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 (Словари). Оба будут работать.

Другие вопросы по тегам