Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

subscription support #12

Open
jaredh159 opened this issue Oct 18, 2021 · 6 comments
Open

subscription support #12

jaredh159 opened this issue Oct 18, 2021 · 6 comments

Comments

@jaredh159
Copy link
Contributor

It looks like this library in its current form doesn't support GraphQL subscriptions. Is that correct? I'm wanting to implement some new websocket-based functionality in one of my vapor/graphql apps, and I'm trying to get a feel for how much work it would be to keep it purely graphql, or whether I should just build this feature outside of graphql for now. I know the Graphiti library has some support for subscriptions, by subclassing EventStream from the core GraphQL library.

have you thought about this at all, or do you have any plans on implementing this? or thoughts on how it would be accomplished? if the path forward seemed not to crazy, i thought maybe i could possibly try an initial implementation, if it was something you were interested in adding support for, or could possibly give me a pointer or two how you'd like to see it implemented in the context of this library.

@alexsteinerde
Copy link
Owner

That's right. This package doesn't have any additional GraphQL subscription functionality. But as the underlying Graphiti and GraphQL packages support subscriptions, you can always use these to implement them in your application.
If you like to create a PR with a generic implementation that helps others to implement GraphQL subscriptions with Vapor faster, that would be great.
I haven't looked into GraphQL subscriptions myself, so I'm not very familiar with them.

@jaredh159
Copy link
Contributor Author

Thanks. I looked into this for a few hours, and I don't think I'm going to tackle it right now, it's just a bit above my skill set currently. But if I gain some clarity and understanding in the future, I'd really like to convert my web-socket functionality to GraphQL subscriptions, so I may take a crack at it later.

The underlying mechanisms provided by the Graphiti and GraphQL packages are only scantily documented. I read and re-read the source for those a bunch of times. I think I got a very basic mental model of how it works -- your resolver needs to return a Future of an EventStream<Any> (which you need to subclass yourself, or pull in a third party package). But implementing the event stream source itself from scratch seems daunting. The documentation recommends pulling in some sort of reactive library, which seems like the way to go. I would lean towards using Combine, but since it doesn't exist on Linux, OpenCombine seems ideal. But I'm just a beginner with Combine, and it's got a bit of a steep learning curve. It seems that one would somehow have to return some kind of Publisher, which publishes values over a web-socket. But how to connect the web-socket connection with an Observable object and keep all those connections straight and thread-safe and connected with the GraphQL schema, seems unclear right now. I'd be curious to know if @NeedleInAJayStack (who implemented the feature in both libraries) built his final production use case in Vapor (or with web sockets as the transport), and had any insight on how to pull it off.

I'm going to implement vanilla web-sockets without GraphQL in my vapor app for now, and my hope is that as my comfort level with those grows (this is my first production implementation of web sockets), and as I learn more about Combine, I might see a way forward.

In the meantime, if you ever want to work on this feature, I'd be happy to try to help, or do some remote pair programming. Really appreciate this library.

@NeedleInAJayStack
Copy link

NeedleInAJayStack commented Oct 29, 2021

Sure, I can provide a little insight. I did build my final app using the normal Vapor websocket implementation, but I didn't use this package - just Graphiti.

In terms of connecting up the observables to websockets, it's not too bad. The steps are this:

  1. Create a publisher in the subscription field resolver and set it up to publish when a thing happens (in our case, we just publish inside a SQL trigger callback). This publisher is our EventStream.
  2. Have a Vapor websocket endpoint that clients can connect to and initialize subscriptions.
  3. When a client requests a subscription, call API.executeSubscription. This returns a transformed EventStream that emits fully-resolved graphQL results.
  4. We can add any callbacks we want onto this transformed EventStream, like writing out to the WebSocket that the client initiated.

In my implementation, I copied the protocol used by the javascript graphql-ws to formalize the websocket handshake and make it work with GraphiQL.

The small GraphQLRxSwift package is a wrapper around RxSwift's PubSub functionality - it should be almost exactly the same for a Combine or OpenCombine implementation. Really all that's needed is a "transformation" operator on observables so we can transform raw resolver events into fully-resolved GraphQL results.

@jaredh159
Copy link
Contributor Author

@NeedleInAJayStack really appreciate you taking the time to chime in. 🙏

So, is there a single, shared, singleton-like publisher for your whole app that publishes when something happens (via the SQL trigger)? Do you then end up basically filtering over the single publisher and emitting events interesting to given web-socket clients based on what they subscribed to?

Or am I misunderstanding, and does each client websocket connection get its own un-shared publisher based on the subscription?

I don't suppose there's any chance your app is open source somewhere where I could poke around the code a bit? If not, would you mind copy/pasting a few lines of the relevant code for each step you mentioned above?

Thanks again!

@NeedleInAJayStack
Copy link

NeedleInAJayStack commented Oct 29, 2021

No problem!

So, is there a single, shared, singleton-like publisher for your whole app

No, we create a fresh PublishSubject in each GraphQL subscription resolver. This is because we want to create and setup SQL triggers based only on what clients have asked for, and then just wrap their callbacks with RxSwift. Yea, the GraphQLRxSwift tests are a little confusing in this case because they use a global PublishSubject because the tests need to have an external handle to cause an event.

Or am I misunderstanding, and does each client websocket connection get its own un-shared publisher based on the subscription?

Yes, this is what we do.

I don't suppose there's any chance your app is open source

Unfortunately it's closed source. The Graphiti GraphQLRxSwift tests are pretty close to what we are doing though, except that in the subscribe resolver we create the PublishSubject, activate a SQL trigger, and then in the trigger event callback we call PublishSubject.onNext. It's probably worth noting that we aren't using Fluent and I don't know much about their SQL triggering mechanism.

Hope that helps!

@jaredh159
Copy link
Contributor Author

Thanks a lot @NeedleInAJayStack, that helps a lot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants