ランタイムアーキテクチャ
Relayランタイムは、ローエンドのモバイルデバイスでも高性能を発揮するように設計された、フル機能のGraphQLクライアントであり、大規模で複雑なアプリケーションにも対応できます。ランタイムAPIは、製品コードで直接使用することを意図したものではなく、React/Relayなどの高レベルの製品APIを構築するための基盤を提供するためのものです。この基盤には以下が含まれます。
- 正規化された、インメモリオブジェクトグラフ/キャッシュ。
- クエリ/ミューテーション/サブスクリプションの結果でキャッシュを更新するための最適化された「書き込み」操作。
- キャッシュからデータを読み取り、ミューテーション、サブスクリプションの更新などによってこれらの結果が変更されたときに更新をサブスクライブするメカニズム。
- ビューから参照できなくなったエントリをキャッシュから削除するガベージコレクション。
- キャッシュに公開する前にデータをインターセプトし、新しいデータを合成するか、新しいデータと既存のデータをマージする汎用メカニズム(これにより、さまざまなページネーションスキームを作成できます)。
- 楽観的更新と、任意のロジックでキャッシュを更新する機能を備えたミューテーション。
- ネットワーク/サーバーでサポートされている場合、ライブクエリをサポートします。
- サブスクリプションを有効にするためのコアプリミティブ。
- オフライン/永続キャッシュを構築するためのコアプリミティブ。
データ型
DataID
(型): レコードのグローバルに一意またはクライアント生成の識別子。文字列として格納されます。Record
(型): ID、型、およびフィールドを持つ個別のデータエンティティの表現。実際のランタイム表現はシステムに対して不透明であることに注意してください。Record
オブジェクトへのすべてのアクセス(レコードの作成を含む)は、RelayModernRecord
モジュールを介して行われます。これにより、表現自体を1か所(たとえば、Map
またはカスタムクラスを使用する)で変更できます。他のコードが、Record
が常にプレーンオブジェクトであると想定しないことが重要です。RecordSource
(型): データIDをキーとするレコードのコレクション。キャッシュとその更新を表すために使用されます。たとえば、ストアのレコードキャッシュはRecordSource
であり、クエリ/ミューテーション/サブスクリプションの結果は、ストアに公開されるRecordSource
に正規化されます。ソースはまた、オフラインユースケースを(最終的に)サポートするために、レコードを非同期にロードするためのメソッドも定義します。現在、このインターフェースの唯一の実装はRelayInMemoryRecordSource
です。将来の実装では、ディスクからレコードをロードするためのサポートが追加される可能性があります。Store
(型):RelayRuntime
インスタンスの真実のソース。RecordSource
の形式で正規のレコードセットを保持します(ただし、これは必須ではありません)。現在、唯一の実装はRelayModernStore
です。Network
(型): 外部データソースからクエリデータを取得し、ミューテーションを実行するためのメソッドを提供します。Environment
(型):Store
とNetwork
を組み合わせたカプセル化された環境を表し、両方と対話するための高レベルAPIを提供します。これは、RelayRuntime
の主要なパブリックAPIです。
クエリとその結果を操作するための型には、以下が含まれます。
Selector
(型): セレクターは、サブグラフをターゲットにするためにグラフをトラバースするための開始点を定義します。GraphQLフラグメント、変数、およびトラバーサルが進むルートオブジェクトのデータIDを組み合わせます。直感的に、これはオブジェクトグラフの一部を「選択」します。Snapshot
(型): 特定の時点でのSelector
の実行の(不変の)結果。これには、セレクター自体、その実行結果、およびデータが取得されたデータIDのリスト(これらの結果がいつ変更されるかを判断するのに役立ちます)が含まれます。
データモデル
Relayランタイムは、オブジェクトが型、ID、および値を持つフィールドのセットを持つ**オブジェクトグラフ**を記述するGraphQLスキーマで使用することを目的としています。オブジェクトは相互に参照できます。これは、値がグラフ内の1つ以上の他のオブジェクトであるフィールドによって表されます[1]。JavaScriptのObject
と区別するために、これらのデータ単位はRecord
と呼ばれます。Relayは、内部キャッシュとクエリ/ミューテーション/などの結果の両方を、**データID**から**レコード**へのマッピングとして表します。データIDは、レコードの一意の(キャッシュに関して)識別子です。実際のid
フィールドの値である場合もあれば、id
を持つ最も近いオブジェクトからのレコードへのパスに基づく場合もあります(このようなパスベースのIDは**クライアントID**と呼ばれます)。各Record
は、データID、型、およびフェッチされたフィールドを格納します。複数のレコードは、データIDからRecord
インスタンスへのマッピングであるRecordSource
として一緒に格納されます。
たとえば、ユーザーとその住所は次のように表される場合があります。
// GraphQL Fragment
fragment on User {
id
name
address {
city
}
}
// Response
{
id: '842472',
name: 'Joe',
address: {
city: 'Seattle',
}
}
// Normalized Representation
RecordSource {
'842472': Record {
__id: '842472',
__typename: 'User', // the type is known statically from the fragment
id: '842472',
name: 'Joe',
address: {__ref: 'client:842472:address'}, // link to another record
},
'client:842472:address': Record {
// A client ID, derived from the path from parent & parent's ID
__id: 'client:842472:address',
__typename: 'Address',
city: 'Seattle',
}
}
[1]GraphQL自体はこの制約を課していないことに注意してください。Relayランタイムは、この制約に準拠していないスキーマにも使用できます。たとえば、どちらのシステムも、単一の非正規化テーブルのクエリに使用できます。ただし、Relayランタイムが提供するキャッシングや正規化などの多くの機能は、データが個別の情報の安定したIDを持つ正規化されたグラフとして表される場合に最適に機能します。
ストア操作
Store
はアプリケーションデータの真実のソースであり、次のコア操作を提供します。
lookup(selector: Selector): Snapshot
: ストアからセレクターの結果を読み取り、ストアに現在あるデータに基づいて値を返します。subscribe(snapshot: Snapshot, callback: (snapshot: Snapshot) => void): Disposable
: セレクターの結果の変更をサブスクライブします。コールバックは、スナップショットのセレクターの結果が変更されるデータがストアに公開されたときに呼び出されます。publish(source: RecordSource): void
: ストアを新しい情報で更新します。クエリ/ミューテーション/サブスクリプションの結果と楽観的ミューテーション更新を含む、ストアへのすべての更新はこの形式で表されます。これらの操作はすべて、内部で新しいRecordSource
インスタンスを作成し、最終的にストアに公開します。publish()
は、subscribe()
したユーザーをすぐに更新*しない*ことに注意してください。内部的には、ストアは新しいRecordSource
を内部ソースと比較し、必要に応じて更新します。- 公開されたソースにのみ存在するレコードは、ストアに追加されます。
- 両方に存在するレコードは、新しいレコード(入力は変更されない)にマージされ、結果がストアに追加されます。
- 公開されたソースでnullであるレコードは、ストアで削除(nullに設定)されます。
- 特別なセンチネル値を持つレコードは、ストアから削除されます。これは、楽観的に作成されたレコードの公開を取り消すことをサポートします。
notify(): void
: 介入するpublish()
によって結果が変更されたsubscribe()
したユーザーのコールバックを呼び出します。publish()
とnotify()
を分離することで、ダウンストリームの更新ロジック(レンダリングなど)を実行する前に、複数のペイロードを公開できます。retain(selector: Selector): Disposable
: 指定されたセレクターを満たすために必要なすべてのレコードがメモリ内に保持されるようにします。返された参照が破棄されるまで、レコードはガベージコレクションの対象になりません。
データフローの例: クエリデータのフェッチ
┌───────────────────────┐
│ Query │
└───────────────────────┘
│
▼
┌ ─ ─ ─ ┐
fetch ◀────────────▶ Server
└ ─ ─ ─ ┘
│
┌─────┴───────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ Query │ │ Response │
└──────────┘ └──────────┘
│ │
└─────┬───────┘
│
▼
normalize
│
▼
┌───────────────────────┐
│ RecordSource │
│ │
│┌──────┐┌──────┐┌─────┐│
││Record││Record││ ... ││
│└──────┘└──────┘└─────┘│
└───────────────────────┘
- クエリはネットワークからフェッチされます。
- クエリとレスポンスは一緒にトラバースされ、結果が新しい
RecordSource
に追加されるRecord
オブジェクトに抽出されます。
この新しいRecordSource
は、ストアに公開されます。
publish
│
▼
┌───────────────────────────┐
│ Store │
│ ┌───────────────────────┐ │
│ │ RecordSource │ │
│ │ │ │
│ │┌──────┐┌──────┐┌─────┐│ │
│ ││Record││Record││ ... ││ │ <--- records are updated
│ │└──────┘└──────┘└─────┘│ │
│ └───────────────────────┘ │
│ ┌───────────────────────┐ │
│ │ Subscriptions │ │
│ │ │ │
│ │┌──────┐┌──────┐┌─────┐│ │
│ ││ Sub. ││ Sub. ││ ... ││ │ <--- subscriptions do not fire yet
│ │└──────┘└──────┘└─────┘│ │
│ └───────────────────────┘ │
└───────────────────────────┘
結果を公開するとストアが更新されますが、サブスクライバーにはすぐに通知され*ません*。これは、notify()
を呼び出すことによって行われます...
notify
│
▼
┌───────────────────────────┐
│ Store │
│ ┌───────────────────────┐ │
│ │ RecordSource │ │
│ │ │ │
│ │┌──────┐┌──────┐┌─────┐│ │
│ ││Record││Record││ ... ││ │
│ │└──────┘└──────┘└─────┘│ │
│ └───────────────────────┘ │
│ ┌───────────────────────┐ │
│ │ Subscriptions │ │
│ │ │ │
│ │┌──────┐┌──────┐┌─────┐│ │
│ ││ Sub.││ Sub.││ ...││ │ <--- affected subscriptions fire
│ │└──────┘└──────┘└─────┘│ │
│ └───┼───────┼───────┼───┘ │
└─────┼───────┼───────┼─────┘
│ │ │
▼ │ │
callback │ │
▼ │
callback │
▼
callback
...これは、結果が変更されたsubscribe()
したユーザーのコールバックを呼び出します。各サブスクリプションは次のようにチェックされます。
- まず、最後の
notify()
以降に変更されたデータIDのリストが、サブスクリプションの最新のSnapshot
にリストされているデータIDと比較されます。重複がない場合、サブスクリプションの結果は変更されません(グラフを視覚的に想像すると、変更されたグラフの部分と選択された部分に重複がありません)。この場合、サブスクリプションは無視されます。それ以外の場合は処理が続行されます。 - 次に、データIDが重複するサブスクリプションはすべて再読み込みされ、新しい/以前の結果が比較されます。結果が変更されていない場合、サブスクリプションは無視されます(これは、サブスクリプションのセレクターに関連しないレコードのフィールドが変更された場合に発生する可能性があります)。それ以外の場合は処理が続行されます。
- 最後に、データが実際に変更されたサブスクリプションは、コールバックを介して通知されます。
データフローの例: ストアの読み取りと監視
製品は、主に lookup()
と subscribe()
を介してストアにアクセスします。lookup はフラグメントの初期結果を読み取り、subscribe はその結果の変更を監視します。lookup()
の出力(Snapshot
)は subscribe()
の入力になることに注意してください。これは、スナップショットにサブスクリプションを最適化するために使用できる重要な情報が含まれているため重要です。subscribe()
が Selector
のみを引数として受け入れる場合、何をサブスクライブするかを知るために結果を再読み込みする必要があり、効率が低下します。
そのため、典型的なデータフローは次のようになります。このフローは、React/Relay などの高レベル API によって自動的に管理されることに注意してください。最初に、コンポーネントはレコードソース(例:ストアの canonical ソース)に対してセレクターの結果を lookup します。
┌───────────────────────┐ ┌──────────────┐
│ RecordSource │ │ │
│ │ │ │
│┌──────┐┌──────┐┌─────┐│ │ Selector │
││Record││Record││ ... ││ │ │
│└──────┘└──────┘└─────┘│ │ │
└───────────────────────┘ └──────────────┘
│ │
│ │
└──────────────┬────────────┘
│
│ lookup
│ (read)
│
▼
┌─────────────┐
│ │
│ Snapshot │
│ │
└─────────────┘
│
│ render, etc
│
▼
次に、変更の通知を受けるために、このスナップショットを使用して subscribe()
を実行します。publish()
と notify()
については、上記の図を参照してください。
このページは役に立ちましたか?
いくつかの簡単な質問に答えることで、サイトの改善にご協力ください。 簡単な質問に答えて.