Subscriptions are really hard. That’s what makes them fun. It’s been a few months since & Relay have supported them, and still examples are sparse. The worst part? The ones that do exist use patterns that will give you a headache if you try to implement them in your largescale app. Trust me. GraphQL After a whole lot of trial and error, I finally came across a pattern that scales linearly with your app. Hopefully, it’ll keep you from suffering as you embark on real-timeifying your app. Some Background The holy grail is a page where every piece of domain state updates in real-time, the code is maintainable, and there is no overfetching. That’s as true today as it has been since the first AJAX request. After all, pseudo-real-time apps have been around for decades. You send an AJAX every 5 seconds for everything on the screen and you’re there. The problem is that when it really counts, 5 seconds is too long, and when activity is sparse, 5 seconds is too short. Subscriptions fixed this because instead of dry humping the server ad infinitum, the server tells you what’s changed. Meteor was the first framework to really leverage this using what’s called a LiveQuery. It tailed the Oplog and pushed the changes to subscribed clients. RethinkDB later perfected this by building a natively reactive database. Both strategies are good, but suffer from 2 problems: MongoDB First, the overfetching: If you care about the of a and someone changes the , you’re still handling the whole gosh darn document. Sure you pluck individual fields before you send to the client, but then that subscription cannot be reused across the app, and from there it’s overfetching the whole way down. content Todo priority could Second, and most importantly, they’re limited by how the data is stored. If you care about a as well as the number of Todos they are have, you either need to denormalize that count onto the table, or you need to also subscribe to a count and patch the 2 together. Back-end changes to accommodate the front-end? That’s a code smell. Team Team Todo Enter GraphQL GraphQL changed all this with their newfangled data-transform pipelines. It can even be used for a like those mentioned above, although I don’t recommend it (to learn why, I highly recommend the ). With GraphQL, a subscription is no longer limited by how it is stored in a database because each subscription triggers a client-defined query. The only problem left to solve is how to segment subscriptions. After talking to a lot of folks, reading a lot of code, and trying a lot of stupid things, I learned there are 3 ways to segment subscriptions: . Let’s look at each. LiveQuery awesome talk from GraphQL Summit per-query, per-entity, and per-mutation Per-query Subscriptions The siren of the bunch. You just got the thumbs up to make a piece of your app reactive so you build a subscription around a single component. If any data in that component’s query changes, your subscription will let you know with a single beautiful payload. Unfortunately, your marketing team then changes the layout of the page and you quickly learn that as your components change, your server must change. Updating 10 mutations and your subscriptions because a new got added is lame, so you hunt on for a better pattern. <Footer/> Per-Entity Subscriptions This type of subscription is the most popular. From PubSub textbooks to GraphQL example repos, you see it everywhere. It’s pretty simple: if you have a mutation that modifies a with ID of , then you publish a message to in perfect fashion. Simple, right? Well, until you call an mutation. If you publish to channel , no one will be listening yet, so you’ll need to post it to parent channel, such as . Another channel just for listening to teams you get added to? Not great, but not terrible. Team 123 Team.123 Topic.Channel AddTeam Team.124 Team.userId Next, assume that you have a mutation that removes you from every and for every team, removes each item. The poor client listening on the and channels is going to receive 1 message for each Todo, Team, and company; That’s m*n + m + 1 messages! A client handling 100 updates in quick succession may drop below 60fps, but again, not terrible. A smart batching strategy can easily mitigate this. RemoveFromCompany Team Todo Team Todo Third, and here’s where it gets fun, is that per-entity subscriptions inherently overfetch. Imagine a mutation. You only change a single field, but the subscription returns the entire object because you share it with other mutations. Sure, some of those fields may be expensive to fetch, but what’s a little overfetching if it means the code is maintainable… ChangeTeamName Team Unfortunately, maintainability is a nightmare, which brings us to the real reason why per-entity subscriptions are bad news: they transfer , not the . For example, if I pop a toast when a gets removed, will trigger 100 toasts? If a user got added to a team, are they brand new, or did they reactivate? Who added them? Was it you from your cellphone while you’re looking at the same page on your computer? At the end of the day, I came to the harsh realization that I needed more than just state. I needed the event. state event Todo RemoveFromCompany Before realizing what the problem was, I solved this by bifurcating state and event into separate and channels. I told myself the channel would handle updates to the object, and the channel would handle any toasts or one-off messages. As the app grew and business logic changed, I realized the payload often contained the entire object and I didn’t even need the channel! In fact, the payload was almost identical to the payload of the mutation that triggered it, yet here I was working tirelessly to squash bugs to keep the 3 separate queries and handlers returning the same result. Like an idiot. Team Event Team Team Event Event Team Team Event Per-mutation subscriptions A subscription is a mutation you didn’t know you wanted. With that in mind, it makes perfect sense to subscribe to a mutation. Imagine a subscription payload that looks identical to the mutation payload, sharing a single handler and query fragment. When business logic changes, you go to the single source of truth to update. Unfortunately, where per-query subscriptions fail because they require constant changes to the back-end, per-mutation subscriptions fail because they require constant changes to the front-end. Imagine if you had a single channel for each mutation, like . Any component that used the field would also have to subscribe to it. Looks like another dead-end for maintainability. ChangeTeamName.123 team.name Secondly, we’re still overfetching. The mutation payload likely provides more data than the component needs. For example, the payload might include , but your component only cares about the that got removed. Do you chose to overfetch, or do you write a second handler? RemoveFromCompany teams TodoList Todos Hybrid Subscriptions If you already have subscriptions, you probably implement some sort of hybrid without realizing it. Either you have separate ADD/REMOVE/UPDATE subscriptions and subscribe to all 3 simultaneously on the client, or your subscription payload is a union of an added item, removed item, or updated item, or you just fetch the whole item regardless of the type (which gets tricky for deleted items!). Regardless, both per-entity and per-mutation subscriptions lack maintainability and suffer from overfetching, but in opposing ways. Back in the days of fixed-payload subscriptions, we had to pick one. Thankfully, this is where GraphQL saves the day. We can take the best parts of per-entity and per-mutation strategies and create a new type of hybrid subscription that has never been possible, … until now … just kidding! Paywalls suck, Medium. How to implement Hybrid Subscriptions Let’s start with the server. In the mutation, we return a like . We know publishing to a channel like will make life hard for the front-end developers, so instead, we publish to . Why use entity-based channels? Because it’s the perfect compromise. If we published everything to a single channel, then we’d be sending the user every message, even the ones don’t don’t affect her current view. She probably doesn’t care about an updated item if she isn’t currently looking at her list of todos! Conversely, it’s cruel to make a component rely on an ever-changing list of mutations that affect the team; but and components that both subscribe to a subscription? Yeah, that’s manageable. ChangeTeamName ChangeTeamNamePayload team { name } ChangeTeamName.123 Team.123 Todo <Team/> <Team/> <TodoList/> Team Next, the only thing to add to your call to is the mutation name: . The subscription payload is simply a based on this type. It’s so easy it’s like the Betty Crocker of real-time apps. publish publish(Team.123, {team, type: ChangeTeamNamePayload}) union that resolves the concrete type Since mutations and subscriptions return the same object type, they can share handlers. Since subscriptions do the grouping on the server, components don’t have to. All that’s left to fix is the overfetching. To solve this, I break each mutation query up into . For example, standalone fragments fragment ChangeTeamNameMutation_team on ChangeTeamNamePayload {team {name}}ChangeTeamNameMutation {...ChangeTeamNameMutation_team}TeamSubscription {...ChangeTeamNameMutation_team...ChangeTeamColorMutation_team} As seen, in the subscription I include every fragment with a _team suffix. Not too hard, but a codemod could make it even easier (*hint hint*). Writing per-channel fragments and handlers makes great sense because 1 mutation calls many subscription channels, and 1 subscription is triggered by many mutations. Since GraphQL lets you fragment on type, this isn’t a problem. Even for mutations that can return widely varying payloads (ie a mutation that either adds or removes) you can still employ unions and interfaces. Even better, it means if all your business logic lives in the same handler. Need to pop a toast to the mutator, quietly update the state on the mutator’s other devices, and announce a separate message to the rest of the team? No sweat. Here’s . ToggleTeam what it looks like in production Conclusion Hope this inspires you to add some real-time functionality to your app! Thanks to GraphQL, I’m able to write subscriptions that use the same queries and handlers as my mutations, which means they’re maintainable, have drastically reduced overfetching, and best yet, the pattern scales modularly so you can make your app reactive 1 mutation at a time, which should make your boss happy. Working on something similar? Reach out!