|
| 1 | +--- |
| 2 | +layout: guide |
| 3 | +doc_stub: false |
| 4 | +search: true |
| 5 | +section: Subscriptions |
| 6 | +title: Broadcasts |
| 7 | +desc: Delivering the same GraphQL result to multiple subscribers |
| 8 | +index: 3 |
| 9 | +--- |
| 10 | + |
| 11 | +GraphQL-Ruby 1.11+ introduced a new algorithm for tracking subscriptions and delivering updates, _broadcasts_. |
| 12 | + |
| 13 | +A broadcast is a subscription update which is executed _once_, then delivered to _any number_ of subscribers. This reduces the time your server spends running GraphQL queries, since it doesn't have to re-run the query for every subscriber. |
| 14 | + |
| 15 | +But, __take care__: this approach risks leaking information to subscribers who shouldn't receive it. |
| 16 | + |
| 17 | +## Setup |
| 18 | + |
| 19 | +To enable broadcasts, add `broadcast: true` to your subscription setup: |
| 20 | + |
| 21 | +```ruby |
| 22 | +class MyAppSchema < GraphQL::Schema |
| 23 | + # ... |
| 24 | + use GraphQL::Execution::Interpreter |
| 25 | + use GraphQL::Analysis::AST |
| 26 | + use SomeSubscriptionImplementation, |
| 27 | + broadcast: true # <---- |
| 28 | +end |
| 29 | +``` |
| 30 | + |
| 31 | +Then, any broadcastable field can be configured with `broadcastable: true`: |
| 32 | + |
| 33 | +```ruby |
| 34 | +field :name, String, null: false, |
| 35 | + broadcastable: true |
| 36 | +``` |
| 37 | + |
| 38 | +When a subscription comes in where _all_ of its fields are `broadcastable: true`, then it will be handled as a broadcast. |
| 39 | + |
| 40 | +Additionally, you can set `default_broadcastable: true`: |
| 41 | + |
| 42 | +```ruby |
| 43 | +class MyAppSchema < GraphQL::Schema |
| 44 | + # ... |
| 45 | + use GraphQL::Execution::Interpreter |
| 46 | + use GraphQL::Analysis::AST |
| 47 | + use SomeSubscriptionImplementation, |
| 48 | + broadcast: true, |
| 49 | + default_broadcastable: true # <---- |
| 50 | +end |
| 51 | +``` |
| 52 | + |
| 53 | +With this setting, fields are broadcastable by default. Only a field with `broadcastable: false` in its configuration will cause a subscription to be handled on a subscriber-by-subscriber basis. |
| 54 | + |
| 55 | +## What fields are broadcastable? |
| 56 | + |
| 57 | +GraphQL-Ruby can't infer whether a field is broadcastable or not. You must configure it explicitly with `broadcastable: true` or `broadcastable: false`. (The subscription plugin also accepts `default_broadcastable: true|false`.) |
| 58 | + |
| 59 | +A field is broadcastable if _all clients who request the field will see the same value_. For example: |
| 60 | + |
| 61 | +- General facts: celebrity names, laws of physics, historical dates |
| 62 | +- Public information: object names, document updated-at timestamps, boilerplate info |
| 63 | + |
| 64 | +For fields like this, you can add `broadcastable: true`. |
| 65 | + |
| 66 | +A field is __not broadcastable__ if its value is different for different clients. For example: |
| 67 | + |
| 68 | +- __Viewer-specific information:__ if a field is specifically viewer-based, then it can't be broadcasted to other viewers. For example, `discussion { viewerCanModerate }` might be true for a moderator, but it shouldn't be broadcasted to other viewers. |
| 69 | +- __Context-specific information:__ if a field's value takes the request context into consideration, it shouldn't be broadcasted. For example, IP addresses or HTTP header values probably can't be broadcasted. If a field reflects the viewer's timezone, it can't be broadcasted. |
| 70 | +- __Restricted information:__ if some viewers see one value, while other viewers see a different value, then it's not broadcastable. Broadcasting this data might leak private information to unauthorized clients. (This includes filtered lists: if the filtering is viewer-by-viewer, it's not broadcastable.) |
| 71 | +- __Fields with side effects:__ if the system requires a side effect (eg, logging a metric, updating a database, incrementing a counter) whenever a resolver is executed, it's not a good candidate for broadcasting because some executions will be optimized away. |
| 72 | + |
| 73 | +These fields can be tagged with `broadcastable: false` so that GraphQL-Ruby will handle them on a subscriber-by-subscriber basis. |
| 74 | + |
| 75 | +If you want to use subscriptions but have a lot of non-broadcastable fields in your schema, consider building a new set of subscription fields with limited access to other schema objects. Instead, optimize those subscriptions for broacastability. |
| 76 | + |
| 77 | +## Under the Hood |
| 78 | + |
| 79 | +GraphQL-Ruby determines which subscribers can receive a broadcast by inspecting: |
| 80 | + |
| 81 | +- __Query string__. Only exactly-matching query strings will receive the same broadcast. |
| 82 | +- __Variables__. Only exactly-matching variable values will receive the same broadcast. |
| 83 | +- __Field and Arguments__ given to `.trigger`. They must match the ones initially sent when subscribing. (Subscriptions always worked this way.) |
| 84 | +- __Subscription scope__. Only clients with exactly-matching subscription scope can receive the same broadcasts. |
| 85 | + |
| 86 | +So, take care to {% internal_link "set subscription_scope", "subscriptions/subscription_classes#scope" %} whenever a subscription should be implicitly scoped! |
| 87 | + |
| 88 | +(See {{ "GraphQL::Subscriptions::Event#fingerprint" | api_doc }} for the implementation of broadcast fingerprints.) |
0 commit comments