web-dev-qa-db-ja.com

ServiceStack.Net Redis:関連オブジェクトと関連オブジェクトIDの保存

私のチームは、ServiceStack.net Redis Clientを介して、作業中の新しい大量のWebサイトの基盤となるリポジトリとしてRedisを使用することを決定しました。この質問のドキュメントを探す場所が本当にわからない(一般的なRedisドキュメントまたは特定のServiceStack.Netドキュメント、あるいはその両方)-ServiceStack.Netを介してRedisを実装する方法に関するドキュメントの決定的なソースが実際にあります。 Redisの概念とServiceStack.Netの概念の両方について知っておく必要があるすべてのこと、または全体像を把握するために両方の側面のドキュメントを個別に統合する必要がありますか。

関連するオブジェクトをモデルのオブジェクトグラフに正確に格納する方法に取り組んでいます。これが私が使用したい簡単なシナリオです:

システムにはUserFeedの2つのオブジェクトがあります。 RDBMSの用語では、これらの2つのオブジェクトには1対多の関係があります。つまり、UserFeedオブジェクトのコレクションを持ち、フィードは1つのUserにのみ属することができます。フィードは常にユーザーを介してRedisからアクセスされますが、フィードインスタンスを介してユーザーにアクセスしたい場合があります。

したがって、私が持っている問題は、関連オブジェクトをプロパティとして保存する必要があるのか​​、または関連オブジェクトのId値を保存する必要があるのか​​ということです。説明する:

アプローチ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; }
}

アプローチ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 );
    }
}

上記のアプローチのどれが最もよく機能しますか?さまざまな例で両方のアプローチが使用されているのを見てきましたが、見た例の一部がベストプラクティスではない可能性があるという印象を受けます。

いくつかの簡単な関連質問:

  • オブジェクトを変更すると、自動的にRedisに反映されますか、それとも保存が必要ですか?私は後者を想定していますが、完全に明確にする必要があります。
  • アプローチAを使用できる(できる)場合、ユーザーオブジェクトXへの更新は、参照されているオブジェクトグラフ全体に反映されますか、それともグラフ全体の変更を保存する必要がありますか?
  • インターフェースを介してオブジェクトを保存することに問題はありますか(つまり、IList<Feed> とは対照的に List<Feed>

これらの質問が少し基本的なものである場合は申し訳ありません-2週間前までは、ServiceStackはもちろんのこと、Redisについても聞いたことがありませんでした。

39
Zac Seth

実際に公開されている他の多くのドキュメントを再ハッシュするのではなく、Redis + ServiceStackのRedisクライアントに関する背景情報をいくつか紹介します。

魔法はありません-Redisは空白のキャンバスです

まず、Redisをデータストアとして使用すると、空白のキャンバスが提供されるだけで、関連するエンティティ自体の概念はないことを指摘しておきます。つまり、分散されたcomp-sciデータ構造へのアクセスを提供するだけです。関係の保存方法は、Redisのプリミティブデータ構造操作を使用することにより、最終的にはクライアントドライバー(ServiceStack C#Redisクライアント)またはアプリ開発者次第です。すべての主要なデータ構造はRedisで実装されているため、基本的に、データの構造と格納方法について完全に自由です。

コードで関係をどのように構成するかを考える

したがって、Redisにデータを格納する方法を考える最良の方法は、データがRDBMSテーブルに格納される方法を完全に無視し、コードに格納する方法を考えることです。つまり、メモリに組み込みのC#コレクションクラスを使用することです。 Redisは、サーバー側のデータ構造と動作をミラーリングします。

関連するエンティティの概念はありませんが、Redisの組み込みSetおよびSortedSetデータ構造は、インデックスを格納するための理想的な方法を提供します。例えば。 RedisのSetコレクションは、要素の最大1オカレンスのみを保存します。つまり、アイテム/キー/ IDを安全に追加でき、アイテムがすでに存在しているかどうかは気にせず、1回または100回呼び出した場合と同じ結果になります。つまり、べき等であり、最終的に1つの要素のみが格納されたままになります。セット。したがって、一般的なユースケースは、オブジェクトグラフ(集約ルート)を保存するときに、モデルを保存するたびに子エンティティID(別名外部キー)をセットに保存することです。

データの視覚化

エンティティがRedisにどのように保存されるかを視覚化するには、 Redis Admin UI をインストールすることをお勧めします。これは、ServiceStackのC#Redisクライアントでうまく機能します。タイプされたエンティティを一緒に(同じグローバルキースペースに存在するすべてのキーにもかかわらず)。

エンティティを表示および編集するには、Editリンクをクリックして、選択したエンティティの内部JSON表現を表示および変更します。うまくいけば、モデルがどのように格納されているかを確認できたら、モデルの設計方法についてより適切な決定を下せるようになるでしょう。

POCO /エンティティの保存方法

C#Redisクライアントは、単一の主キーを持つPOCOで動作します-これはデフォルトでIdであると予想されます(この規則 ModelConfigでオーバーライド可能 )。基本的にPOCOは、typeof(Poco).NameIdの両方がそのインスタンスの一意のキーを形成するために使用されるシリアル化されたJSONとしてRedisに格納されます。例えば:

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

C#クライアントのPOCOは、通常、ServiceStackの高速な Json Serializer を使用してシリアル化されます。ここで、パブリックゲッターを持つプロパティのみがシリアル化されます(およびパブリックセッターがシリアル化されて戻されます)。

デフォルトは_[DataMember]_ attrsでオーバーライド可能ですが、POCOを悪化させるため、推奨されません。

エンティティがブロブされる

したがって、RedisのPOCOはブロブ化されているだけなので、POCOの非集約ルートデータをパブリックプロパティとして保持したいだけです(冗長データを意図的に保存する場合を除きます)。適切な規則は、関連するデータをフェッチするためにメソッドを使用することです(シリアル化されないため)。また、データを読み取るためにリモート呼び出しを行うメソッドをアプリに通知します。

したがって、FeedUserとともに保存されるかどうかに関する質問は、それが非集約ルートデータであるかどうか、つまり、ユーザーのコンテキスト外でユーザーフィードにアクセスするかどうかいいえの場合、Userタイプの_List<Feed> Feeds_プロパティをそのままにします。

カスタムインデックスの維持

ただし、すべてのフィードに個別にアクセスできるようにしたい場合、つまりredisFeeds.GetById(1)を使用する場合は、それをユーザーの外部に格納し、2つのエンティティをリンクするインデックスを維持する必要があります。

お気づきのように、エンティティ間の関係を保存する方法は数多くあり、その方法は主に好みの問題です。 parent> child関係の子エンティティの場合、常にParentId子エンティティ。親の場合、ChildIdsのコレクションをモデルに保存してから、すべての子エンティティに対して単一のフェッチを実行してモデルを再水和することができます。

もう1つの方法は、親インスタンスごとに、それ自体のSetで親dtoの外部のインデックスを維持することです。これのいくつかの良い例が C#ソースコードRedis StackOverflowデモ にあります。ここで_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() AP​​Iを介したデフォルトの親/子規則のサポートが含まれていますが次のように見えます:

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

事実上、これらは可能なオプションのほんの一部であり、同じ目的を達成するための多くの異なる方法があり、自分で自由にロールすることもできます。

NoSQLのスキーマレスでルーズタイピングの自由は受け入れられるべきであり、RDBMSを使用するときに慣れ親しんでいる可能性のある、堅固な事前定義された構造に従うことを心配する必要はありません。

結論として、Redisにデータを保存するための実際の正しい方法はありません。 C#Redisクライアントは、POCOの周りに高レベルのAPIを提供するためにいくつかの仮定を行い、POCOをRedisのバイナリセーフな文字列値にブロブします。 。どちらも機能します。

66
mythz