Skip to content
This repository has been archived by the owner on Apr 14, 2023. It is now read-only.

WIP: Graphql Transport WS Protocol #108

Merged
merged 17 commits into from
May 14, 2017

Conversation

mistic
Copy link
Contributor

@mistic mistic commented Apr 13, 2017

This PR is still a work in progress (with the remain tasks needed to be done described below), that has been done close to @DxCx supervision, but it has already implemented the majority of needed changes to the new package graphql-trasnport-ws according our point 2 on the shared design document ( GraphQL WebSocket transport plan ). So in this PR we have implemented the new protocol in order to support queries, mutations and subscriptions both on server and on the client side (all the code tasks from point 2 of the document are done).

Please note this is an intermediate pull request to allow you guys start reviewing the most important changes. However a final pull request will be opened from a branch called graphql-transport-ws containing the current code of this branch (graphql-transport-ws-protocol) plus the changes you request in this review, unit tests and those following changes:

  • internal code library split into 3 new packages: common, client and server.
  • unit tests split into client and server with a common config.

PS: @DxCx thank you so much for your help, support and continuous reviews during the all process!

TODO:

  • Fix all the old tests.
  • Code tests to cover all the new logic and mapping functions for legacy features.
  • Add env var to change between hybrid/full ws transport, persistedQueries support, max query size limitation, improve code, and write documentation update for full-transport-ws GitHunt repos.
  • Update documentation and also including examples for full and hybrid ws transport on it.
  • Update CHANGELOG.md with changes made.

…s both on client and server side using websockets as transport layer to be used on a new package called graphql-transport-ws.

fix(NA): fixed path of new files.
@apollo-cla
Copy link

@mistic: Thank you for submitting a pull request! Before we can merge it, you'll need to sign the Meteor Contributor Agreement here: https://contribute.meteor.com/

@Urigo
Copy link
Contributor

Urigo commented Apr 18, 2017

@mistic great job! I love your PR and it helps us move a great step forward.

The direction is exactly as discussed on the design doc and you can move forward.

Here are some comments from reviewing the current state:

  • I think we should change GraphQLTransportWSServer to expose static method for creation without constructor because the instance is useless
  • We still want to continue supporting sending queries and mutation over HTTP and subscriptions over Websocket, but now you removed the addGraphQLSubscriptions function. we should keep that functionality, but not here. it is related to Apollo Client network interface and this package is not tied to Apollo. so please create it as a separate repo and npm package for that and I will add it under the Apollo organization when ready.
  • I've tried to run Githunt with your fork but failed with the following error:

screen shot 2017-04-18 at 2 57 53 pm

  • Please also include an example of Githunt running with the package both with complete websockets and hybrid with HTTP and Websocket side by side.
  • backward compatibility Currently the code is not backward compatible because the name changed from SubscriptionServer to GraphQLTransportWSServer and also GraphQLTransportWSClient but, because this would be released as a new npm package, maybe instead of keeping the code back-compack, we can just remove the code and keep the current version on a branch we can support and keep releasing patches from

Great job!

@DxCx
Copy link
Contributor

DxCx commented Apr 19, 2017

regarding backwards compatibility, we should expose the new names by the old names with deprecation warning.

@mistic
Copy link
Contributor Author

mistic commented Apr 20, 2017

@DxCx I've changed the names both on server and on the client back for the old ones as we discussed.

@Urigo Can u review my updates to see if they matches your expectations?

This is an example of how to do hybrid mode (https + ws) or full ws mode with that implementation:

import {SubscriptionClient, addGraphQLSubscriptions} from 'subscriptions-transport-ws';
import ApolloClient, {createNetworkInterface} from 'apollo-client';

// Create WebSocket client
const wsClientOptions = {
	reconnect: true,
	connectionParams: {
    // Pass any arguments you want for initialization
	}
};
const wsNetworkInterface = new SubscriptionClient('ws://localhost:3000/graphql', wsClientOptions);

// Create regular NetworkInterface by using apollo-client's API:
const httpNetworkInterface = createNetworkInterface({
 uri: '/graphql' // Your GraphQL endpoint
});

// Create Hybrid interface
const hybridInterface = addGraphQLSubbscriptions(httpNetworkInterface, wsNetworkInterface)

// Finally, create your ApolloClient instance with the modified network interface
// Full websocket: wsNetworkInterface
// Mixed Mode: hybridInterface
const apolloClient = new ApolloClient({
    networkInterface: hybridInterface
});

PS: I pointed both Githunt-API and Githunt-react for this code and all the old behaviour with subscriptions over ws and queries and mutations over http is working :D At the moment I'm bot able to make a test with GitHunt with full ws transport because it requires some more work.

@mistic mistic force-pushed the graphql-transport-ws-protocol branch 2 times, most recently from 10dafaf to 0fedbd3 Compare April 20, 2017 22:51
src/client.ts Outdated
@@ -159,11 +161,11 @@ export class GraphQLTransportWSClient {
}

if (
!isString(query) ||
( !isString(query) && !getOperationAST(query, operationName)) ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

query instanceof DocumentNode is more cleaner.. 👍

Copy link
Contributor Author

@mistic mistic Apr 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It cannot be done that way as we talked :(

src/helpers.ts Outdated
},
unsubscribe(id: number): void {
wsClient.unsubscribe(id);
},
});
}

export function addGraphQLWS(networkInterface: any, wsClientOptions: ClientOptions): any {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, i might be confused, but why do we need that?

src/helpers.ts Outdated
},
unsubscribe(id: number): void {
wsClient.unsubscribe(id);
},
});
}

export function addGraphQLWS(networkInterface: any, wsClientOptions: ClientOptions): any {
if (typeof networkInterface._uri !== 'string') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are checking if network interface has implementation related uri.. :\

@mistic mistic force-pushed the graphql-transport-ws-protocol branch from 24202de to 4952d35 Compare April 22, 2017 14:42
@mistic
Copy link
Contributor Author

mistic commented Apr 22, 2017

@DxCx @Urigo I've updated my last comment to reflect how we should use this PR to implement the hybrid mode and the full ws mode. Is this according your expectations?

feat(NA): applied changes according pull request review..

feat(NA): added static method for instance creation.

refact(NA): change the class names back to subscriptionserver and subscriptionclient.

feat(NA): applied changes according pull request review.

feat(NA): added static method for instance creation. fix(NA): fixed error thrown when we try to print and query its already a string.

refact(NA): change the class names back to subscriptionserver and subscriptionclient.

feat(NA): added new helper function to fully extend network interface with ws transport. fix(NA): built legacy messages on server side and other bad object accesses.

feat(NA): enclose wsclient creation inside addGraphQLWS function.

fix(NA): removed useless addGraphQLWS function.

feat(NA): added legacy support to the old server protocol.
@mistic mistic force-pushed the graphql-transport-ws-protocol branch from 6ae8112 to 7b19459 Compare April 22, 2017 16:28
src/server.ts Outdated
});
private sendError(connectionContext: ConnectionContext, reqId: string, errorPayload: any,
overrideDefaultErrorType?: string): void {
if ([
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BUG: overrideDefaultErrorType might be undefined.

src/client.ts Outdated
const requestPayloadErrors = parsedMessage.payload.errors ?
this.formatErrors(parsedMessage.payload.errors) : null;

this.requests[reqId].handler(requestPayloadErrors, requestPayloadData);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug: Should be this.requests[reqId].handler(null, parsedMessage.payload);

src/client.ts Outdated
}
}, this.subscriptionTimeout);
return subId;
public subscribe(options: RequestOptions, handler: (error: Error[], result?: any) => void) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here we need to add a wrapper handler for converting payload to:

const requestPayloadData = parsedMessage.payload.data || null;
const requestPayloadErrors = parsedMessage.payload.errors ?
  this.formatErrors(parsedMessage.payload.errors) : null;

to keep backward comptiable with subscription manager

@mistic
Copy link
Contributor Author

mistic commented May 2, 2017

@DxCx @Urigo just merged the changes to support executor pattern. I believe at this point I can start writing the unit tests. What do u think guys? There are anything more to do on the features side?

feat(NA): added subscription function request.

fix(NA): fixed unsubscribe function.

feat(NA): added constructor verification for executor and schema.

chore(NA): added correct comment inside on message function.

feat(NA): added initial filtering for requests.

feat(NA): added initial filtering for requests.

fix(NA): changed way to store unsubscribe function.

fix(NA): removed contextValue from serverOptions.

fix(NA): correct warn comment when using legacy subscription manager,

chore(NA): Implement Adapter pattern for executor

refact(NA): refactor on ExecuteAdapters. feat(NA): added code to support formatError and formatResponse.

fix(NA): fix way we invoke format functions.

fix(NA): small changes on error creation and executor/subscriptionManager check on loadExecutor.

fix(NA): fix bad usage of base params on execute.

feat(NA): updated ws dependency to the last version.

fix(NA):  overrideDefaultErrorType might be undefined.

fix(NA): support for legacy handler.

fix(NA): fixed const and payload errors.
@mistic mistic force-pushed the graphql-transport-ws-protocol branch from ff666f7 to 062d1d9 Compare May 2, 2017 14:21
@Urigo
Copy link
Contributor

Urigo commented May 2, 2017

@mistic let's go ahead and finish the tests.
also if you could link to all the relevant Githunt examples we talked about (and add them to your checklist on the opening comment).
Also, please rebase to master since we merged this PR - #90

@mistic
Copy link
Contributor Author

mistic commented May 2, 2017

@Urigo I'll already merge and solve the conflicts between this branch and last changes on master :D
Regarding to GitHunt can u check my personal messages that I sent you?

I already finished the GitHunt example integration with the full ws transport. You can check it here:

https://github.com/mistic/GitHunt-API/tree/full-transport-ws
https://github.com/mistic/GitHunt-React/tree/full-ws-transport

However in order to be able to make a PR to GitHunt I need to complete some minor details (like a toggle between hybrid and full ws transport on GitHunt). Let's discuss this.

Copy link
Contributor

@helfer helfer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Urigo asked me to review 🙂

Great job so far @mistic! I had a lot of comments, but they are almost all minor (but still important to me, haha).

The biggest thing is probably that since this will go into a different package, and not subscriptions-transport-ws, I don't think we should keep any backwards compatibility. The backwards compatibility can be kept entirely in subscriptions-transport-ws, which will be a deprecated layer on graphql-transport-ws.

Once we merge this PR, we should keep a branch separate from master for subscriptions-transport-ws and rename this project to graphql-transport-ws (to keep the stars, links etc.).

I only had time to review the client, but I'll try to review the server before the weekend as well.

Really great work @mistic and @DxCx !!! 💪

src/client.ts Outdated
import isString = require('lodash.isstring');
import isObject = require('lodash.isobject');
import { ExecutionResult, print, getOperationAST } from 'graphql';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think importing like this will create a huge bundle. We should check the bundle size with a script like we do for react-apollo and apollo-client.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

src/client.ts Outdated
if (!query) {
throw new Error('Must provide `query` to subscribe.');
}
public query(options: RequestOptions): Promise<ExecutionResult> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this function purely for convenience? What if someone submits a live query to it? Or if the document is a subscription? I think the transport should be absolutely minimal and just have an "executeRequest" on it. Everything else can be handled by the caller, which may be a network interface.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well @helfer at the moment we will be compatible with current apollo,
however, it is built in a way that when you will go forward to use 1 method that returns an Observable, the changes here are minimal.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I think if @Urigo prefers it this way, it's fine 🙂 We can rewrite it later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So lets keep this one as is

src/client.ts Outdated
throw new Error('Incorrect option types to subscribe. `subscription` must be a string,' +
'`operationName` must be a string, and `variables` must be an object.');
}
public subscribe(options: RequestOptions, handler: (error: Error[], result?: any) => void) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here as for query. I don't think we need legacy compatibility if we're building a completely new transport. We can provide that legacy compat by making the next update of the old packages wrap the new transport. Or am I missing something?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to change it right now, it's ok to keep it for backcompat.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed let's keep things related with backwards compatibility for now.

@@ -165,95 +149,151 @@ export class SubscriptionClient {
}

public unsubscribe(id: number) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're supporting all kinds of operations, including live queries, then all the operations can be stopped. In that case unsubscribe isn't the right word, so we should use stop or something (for the new transport, not in this package, of course).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to change it right now, it's ok to keep it for backcompat.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed let's keep things related with backwards compatibility for now.

src/client.ts Outdated
if (this.requests[id]) {
delete this.requests[id];
}
this.sendMessage(id, MessageTypes.GQL_END, undefined);
}

public unsubscribeAll() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe stopAll instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to change it right now, it's ok to keep it for backcompat.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed let's keep things related with backwards compatibility for now.

src/client.ts Outdated

// Complete every request that are not registered on the client but are being sent by the server and are not
// in the allowed list
if (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit hard to read. I think if the code was easier to understand, the comment wouldn't be needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

src/helpers.ts Outdated
export function addGraphQLSubscriptions(networkInterface: any, wsClient: SubscriptionClient): any {
console.warn('This method becomes deprecated in the new package graphql-transport-ws. Start using the ' +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make sure this warning is not printed if NODE_ENV is production.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

public static GQL_DATA = 'data';
public static GQL_ERROR = 'error';
public static GQL_COMPLETE = 'complete';
public static GQL_END = 'end';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between COMPLETE and END? If end is to stop an operation, we should call it STOP.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

COMPLETE => Means no data from server
END => Means client doesn't want anymore data.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, let's call that STOP. end is more commonly used as a noun. Stop is almost always a verb.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

public static GQL_ERROR = 'error';
public static GQL_COMPLETE = 'complete';
public static GQL_END = 'end';
// NOTE: Those message types are deprecated and will be removed soon.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the new package we shouldn't have these at all.

Copy link
Contributor

@DxCx DxCx May 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's exactly what this comment means.
but let's finish merging the compatible version first before moving forward

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed let's keep things related with backwards compatibility for now.


// Quick way to add the subscribe and unsubscribe functions to the network interface
// We will move this into a new package in the future
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the new package we shouldn't have this, but we can keep it in the old one and just print the deprecation warning.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed let's keep things related with backwards compatibility for now.

src/server.ts Outdated
if ([
MessageTypes.GQL_CONNECTION_ERROR,
MessageTypes.GQL_ERROR,
].indexOf(overrideDefaultErrorType) === -1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you forgot to use sanitizedOverrideDefaultErrorType in here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah thank you :D

@dotansimha
Copy link
Contributor

@mistic @DxCx @Urigo
Noticed an issue earlier: In some cases, the client disconnects, but the onClose of the client isn't called, and the Network tab of the browser still thinks the client is connected.
The server side tried to publish data to this socket and getting "Not opened" error (the connection state is 3 = CLOSED).

@mistic
Copy link
Contributor Author

mistic commented May 8, 2017

@dotansimha Have u tested with last ws version? Ins't the problem related with ws library itself?
websockets/ws#686

@mistic
Copy link
Contributor Author

mistic commented May 9, 2017

Guys ( @DxCx @helfer @NeoPhi @Urigo @dotansimha or anyone else interested in give some help ) will be really appreciated some help with those 2 tasks:

  • Update documentation and also including examples for full and hybrid ws transport on it.
  • Update CHANGELOG.md with changes made.

…ng on old tests to keep working on new code. fix(NA): error handling between client and server.

refact(NA): applied some changes purposed on pull request.

refact(NA): change executeRequest to executeOperation on client.

fix(NA): apply console warn only outside production env.

fix(NA): import one by one to reduce bundler size.

fix(NA): return original errors format.

test(NA): refact some tests to work with new version.

refact(NA): removed console logs from tests.

test(NA): update old tests.

test(NA): update old tests.

refact(NA): changed requests to operations.

fix(NA): add return to discard invalid messages.

test(NA): fixed bad timeout on test.

test(NA): commented non sense unit test.

test(NA): adapt legacy test to new server code logic with unsubscribe.

test(NA): fixed some legacy tests.

fix(errors): error handling between client & server

chore(NA): added todo about validation error in the server.
@mistic mistic force-pushed the graphql-transport-ws-protocol branch from 2c48656 to 1be31a1 Compare May 11, 2017 16:59
@helfer
Copy link
Contributor

helfer commented May 12, 2017

@mistic wow, great work! I've been really busy myself, so I couldn't help or even review until now. If Uri can't review, I hope I can do it over the weekend.

@mxstbr
Copy link
Contributor

mxstbr commented May 12, 2017

Do I understand correctly that this PR makes it possible to run the GraphQL and the subscriptions server off of the same server and thus the same port? (which is a requirement for deploying on now.sh)

I've tried taking the code sample above and using the branch this PR lives in, but I can't seem to make it work. Are there any docs yet so I can try this out and give some feedback?

@mistic mistic force-pushed the graphql-transport-ws-protocol branch from 407969e to 23cc224 Compare May 12, 2017 16:17
@mistic
Copy link
Contributor Author

mistic commented May 12, 2017

@mxstbr You'll be able to understand how to use this PR looking for those already changed GitHunt example to support this PR:

https://github.com/mistic/GitHunt-API/tree/full-transport-ws
https://github.com/mistic/GitHunt-React/tree/full-ws-transport

@mistic
Copy link
Contributor Author

mistic commented May 12, 2017

Guys ( @DxCx @helfer @NeoPhi @Urigo @dotansimha ) I think we're reaching the end of the work on this PR. Today I merged into this PR the Uri changes to correctly support subscribe method from graphql-js into our server graphql executor. Also I already wrote the new CHANGELOG.md.

Work needed to be done to merge this PR:

  • Write some tests to cover the new logic and the mapping features. All the old ones are already updated and being pass.
  • Update documentation to reflect the new API on server and client and also provide examples.

docs(NA): changelog update.

docs(NA): changelog update.

docs(NA): changelog update.

feat(NA): add subscribe from graphql.

fix(NA): check for connection state inside sendMessage.

fix(NA): merge executeFromSubscribe with executeFromExecute.

feat(NA): changelog update.

fix(NA): clear immediate action on unsubscribe.

fix(NA): removed typo from condition.

fix(NA): added esnext to tslint config.
@mistic mistic force-pushed the graphql-transport-ws-protocol branch from e5db8a9 to a854e33 Compare May 12, 2017 16:54
@Urigo
Copy link
Contributor

Urigo commented May 14, 2017

@mistic amazing amazing work.
I can't thank you enough for all your effort.
There are still more tasks to be done, but I'm going to merge this now because I want @thebigredgeek and @srtucker22 to be able to work and push their changes on top of the new API.

During the next couple of days:

  1. Add tests for the new API - @mistic
  2. Add documentation for the new API - @mistic
  3. @thebigredgeek and @srtucker22 - Rebase your PR against the latest master and the new API.
  4. I will make sure to release a version 0.7.0 that supports the current API without breaking changes, but also include the new API based on the upcoming graphql-js with subscriptions
  5. I will remove all the old API code and will release a new package called graphql-ws-transport

@Urigo Urigo merged commit 0e81e9e into apollographql:master May 14, 2017
@Urigo
Copy link
Contributor

Urigo commented May 16, 2017

@mistic , thank you again for your great PR.
During your work, a new graphql.js subscriptions support came to be.

This changes a bit our goals so I want to reflect on that for a sec, to create an updated path with slight adjustments.

V0.7 goals:

  • Full backward compatibility
  • New API
    • Support query and mutations over WebSocket, with option for hybrid.
    • Support new graphql.js subscriptions with AsyncIterable

Changes by @mistic :

  • Added support for full websocket network interface.
  • Added support for hybrid network interface (using addGraphQLSubscriptions method)
  • Implemented new transport protocol
  • Added ExecuteAdapters to support both old API (SubscriptionsManager) and new API (graphql subscribe)
  • Added executeReactive as default payload source (no need to run ExecuteAdapter), and implemented the transport with Observable base.

On top of those changes, I’ve added the support for the new graphql.js subscriptions implementation:

  • Transport work natively with AsyncIterable for payload source (but requires ExecuteAdapters)
  • Added ability for the transport to expect execute and subscribe directly from graphql-js new subscription implementation.
    • Tiago changed the API from new SubscriptionsClient({ execute, subscribe }) to new SubscriptionsClient({ executer: { execute, subscribe } }) in order to make executeReactive as named priority
  • Transport works with the recent graphql-subscriptions master (with both new and old API).

The problems I have with the current state are those:

  • We understood from the graphql.js subscriptions work that in most cases, we should separate initiating the subscriptions and the firing events Lee’s comment here
  • That means we want to make that common API as simple as possible and the default one instead of the special executeReactive API
  • Once we do that, we should come up with a better design on how to support the executeReactive approach without complexing the common API and implementation

So the changes I'm proposing to do before releasing 0.7 are:

V0.8 goals:

  • Clean and remove all backward compatible code

V0.9 goals:

  • Ability to replace ws library with uws or any other WS implementation library

@DxCx @mistic @helfer @stubailo @NeoPhi I want to hear your opinions on that plan

@DxCx
Copy link
Contributor

DxCx commented May 16, 2017

@Urigo the work on Websocket is done for ~0.5year now,
I've already written it 3 times, while Tiago now did the 4th time with me helping him,

we used observables back then and that's the reason it has observables, AsyncIterator was not in the picture until last month. (more then that, supporting Observables in GraphiQL was already in progress, which made sense why using it).

if you want to change executeReactive signature to return AsyncIterator, I'm totally fine with that because graphql internally started to use, but we shouldn't remove executeReactive, if you have a better proposed solution, let me know.

so, unless you know that plans for how facebook is going to support reactive directives such as @defer or @live we need a reactive execute function to be supported,
and the executor pattern is great because it gives you great flexibility.

about the versioning, generally i agree about the content but i think that V0.8 Should be V1.0 or something like that because it will have a lot of breaking changes.

@Urigo
Copy link
Contributor

Urigo commented May 16, 2017

@DxCx

  1. I totally understand you pain, but let's try to solve this together. that's why I wrote this comment.
  2. Let's separate WebSocket work and @live. WebSocket is now merged and it can work without executeReactive. You now will be able to send queries and mutations over WS as well which is something a lot of people as asked for. So it doesn't matter how long WebSocket work has been done, now it's in master and it can work even without executeReactive.
  3. AsyncIterator wasn't a thing, that's why I'm writing this comment, because now it is a thing. If we really want to build the tools that help GraphQL developers use the main reference implementation and help them do that in production environment with the minimum changes, we should change that API as little as possible. On other libraries we can create completely separate APIs, but those are different libraries with different goals. We should be really careful about what is the goal for each library.
  4. I want to work together in order for you to be able to use as much from this library as possible in your solution, without enforcing everyone to add size and complexity if they are not going to use your solution. I would love to hear more design ideas from you about how to do that. Do you think that there is a solution where you wrap the API that I described in another library and exposing the API you want without hurting the API that 90% of the users will use?

I agree with you about versioning. let's do a v1.0 after v0.7.

@DxCx
Copy link
Contributor

DxCx commented May 16, 2017

alright, @Urigo so as i suggested we can move implementation to async iterator instead of observable, and that will be the smallest change possible, i don't understand why do we need to completely remove executeReactive support if it won't really hurt anyone...

also, it will let people with legacy subscription server upgrade to run mutation + queries with one or two lines of change, not needing to rebuild their whole schemas.

one last thing is that if you can discuess lee about making executeReactive (or any other name you will choose) that accepts executes parameters and return AsyncIterator we can also make sure that the name will less likely to change.
the implementation for such is very easy:

  • check operation if subscription => return subscribe
  • else return AsyncIterator wrapped promise of execute

@mistic
Copy link
Contributor Author

mistic commented May 17, 2017

@Urigo I agree with @DxCx in almost everything!

1 - the v0.8 should be the v1.0.0. It will have a lot of breaking changes and it's almost a completely rewrite. In my opinion is a major version.

2 - Regarding to the SubscriptionsServer, what's the problem with the executor pattern? In my opinion this isn't a deal breaker with anything coming from graphql-js. We just accept an executor in our Server that can be composed by execute & subscribe from graphql-js, by executeReactive from graphql-subscriptions or simply execute from graphql-subscriptions and subscribe from graphql-js. What's the real problem with this thing? Is simply a design choice from our server. In the limit we could provide some predefined executor interfaces to provide more context to the user when instantiating the server.

3 - If you prefer we can move observables to AsyncIterator.

@dotansimha
Copy link
Contributor

@mistic @DxCx @Urigo
What do you think about accepting either Promise or AsyncIterator for execute function?

This means that Hagai can implement @live logic inside a separate package, and expose two functions: execute and subscribe (just like graphql-js does).

The transport can check if the return value is AsyncIterator and wait for reactive changes and publish it over the socket for each value, or use Promise and publish it only once.

@DxCx
Copy link
Contributor

DxCx commented May 17, 2017

@dotansimha that works as well!
my only problem with that is we did not get Lee's confirmation.
which means that we are going to modify a signature of an existing function.
so, if future plans of facebook is to modify execute signature, that's fine.
but if not, it's better to just introduce a new one IMO.
which is essentially the executeReactive introduced here.
as both me & @mistic stated above, there is no problem to eliminate observable reference and support only AsyncIterator

@dotansimha
Copy link
Contributor

@DxCx
We don't need to modify the execute signature in GraphQL. We just need to accept a return value of both options, and handle it correctly inside this transport.
It does not require any modifications in graphql-js, the changes could be in here:
https://github.com/apollographql/subscriptions-transport-ws/blob/master/src/server.ts#L53-L60

And change it to:

export type ExecuteFunction = (
  schema: GraphQLSchema,
  document: DocumentNode,
  rootValue?: any,
  contextValue?: any,
  variableValues?: {[key: string]: any},
  operationName?: string,
) => Promise<ExecutionResult> | AsyncIterator<ExecutionResult>;

This way, graphql-js remains the same, but the transport can support both Promise and AsyncIterable.

What do you think?

@DxCx
Copy link
Contributor

DxCx commented May 17, 2017

@dotansimha i totally understood your meaning, as i said it could work.
but then, don't matter which package will be able to return AsyncIterator as execute, it won't be compatible with other graphql tools except for this package.
the only reason to do it is if reference implementation are going towards this direction as well instead of introducing a new call, but i really don't believe this will happen as it is a breaking change.

@dotansimha
Copy link
Contributor

@DxCx
What about exposing AsyncIterator along with IObservable from packages?

For example, Hagai's ReacticeExecutor can expose execute that returns AsyncIterator along with executeObservable that returns IObservable.

@DxCx
Copy link
Contributor

DxCx commented May 17, 2017

@dotansimha as i mentioned above, i don't mind terminating the observable.
the only intention with observables is to be able to return reactive result. AsyncIterator gives exactly that as well.
the idea is to have an execute signature that also returns an AsyncIterator, if you suggested executeObservable then i really don't see a reason why not keep the existing name of executeReactive which returns AsyncIterator instead of Observable?

@dotansimha
Copy link
Contributor

dotansimha commented May 17, 2017

I meant to expose executeObservable from your GraphQL alternative executer package (the implementation that in your graphlq-subscription PR).

@DxCx
Copy link
Contributor

DxCx commented May 17, 2017

@dotansimha i think there is a confusion in here, this is what i'm trying to achieve:

  1. change exiting executeReactive signature to return AsyncIterator instead of Observable
  2. change all inner Observable implementation to AsyncIterator, removing Observable code completely
  3. change GraphQLExecutorWithSubscription (graphql-subscriptions) to return AsyncIterator and not observable

Do you agree?

@dotansimha
Copy link
Contributor

@DxCx Yeah that's sound great.

Also, I think that since we can remove SubscriptionsManager in a future version, and the dependency for graphql-js in graphql-subscritpions - maybe it's better to implement this in a new package?

I think that the future version of graphql-subscriptions will only contain utils like channel mapping, filters and such.

What do you think?

@DxCx
Copy link
Contributor

DxCx commented May 17, 2017

the idea behaind it was to allow people people that are using graphql-subscriptions today to upgrade easily to full graphql over websocket.
there is no reason for that patch as well if there is no SubscriptionManager.
so, when we clean subscription manager we can clean this too (as long as last supported version of subscription manager has it)

baconz pushed a commit to PhiloInc/subscriptions-transport-ws that referenced this pull request Apr 24, 2018
* chore(package): update graphql to version 0.7.0

https://greenkeeper.io/

* fix failing tests
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants