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

Support RSocket as a transport #339

Closed
rstoyanchev opened this issue Mar 24, 2022 · 16 comments
Closed

Support RSocket as a transport #339

rstoyanchev opened this issue Mar 24, 2022 · 16 comments
Assignees
Labels
type: enhancement A general enhancement
Milestone

Comments

@rstoyanchev
Copy link
Contributor

rstoyanchev commented Mar 24, 2022

The RSocket protocol has all the semantics required to carry GraphQL requests. The request-response interaction maps to queries and mutations while request-stream maps to subscriptions. RSocket supports Reactive Streams signals on the wire to complete a stream from the server side or to cancel it from the client side. That means, GraphQL over RSocket does not require a spec other than agreeing on the format for serialized requests and response. For that we can follow the HTTP spec defined format "application/graphql+json".

On the implementation, this should be very straight-forward, given the transport abstractions we have in place for client and server.

@rstoyanchev rstoyanchev added the type: enhancement A general enhancement label Mar 24, 2022
@rstoyanchev rstoyanchev added this to the 1.0.0-RC1 milestone Mar 24, 2022
@rstoyanchev rstoyanchev self-assigned this Mar 24, 2022
@linux-china
Copy link

linux-china commented Mar 24, 2022

application/graphql+json will be added on https://github.com/rsocket/rsocket/blob/master/Extensions/WellKnownMimeTypes.md as Well-known MIME Type?

I mean RSocket payload's composite metadata contains message/x.rsocket.mime-type.v0 and its value is application/graphql+json

@viglucci
Copy link

What does the +json extension on the proposed mime type denote?

The GraphQL spec states:

A standard GraphQL POST request should use the application/json content type, and include a JSON-encoded body of the following form:

{
  "query": "...",
  "operationName": "...",
  "variables": { "myVariable": "someValue", ... }
}

...

If the "application/graphql" Content-Type header is present, treat the HTTP POST body contents as the GraphQL query string.

I suspect that a graphql RSocket implementation would translate this as either including a metadata mimetype as application/json and using the JSON representation as shown above, or as application/graphql and providing a GraphQL query string as the payload body.

Ex:

query {
  getTask(id: "0x3") {
    id
    title
    completed
    user {
      username
      name
    }
  }
}

@bclozel
Copy link
Member

bclozel commented Mar 28, 2022

@viglucci we are referring here to the GraphQL HTTP spec: https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#content-types

"application/graphql+json" will be the required, default MIME type per that spec. The "+json" suffix is merely a standard way to describe that a MIME type's representation follows a well known format.

See also #108

@viglucci
Copy link

viglucci commented Mar 28, 2022

@bclozel Thanks for the explanation.

Right now RSocket-JS is sending GraphQL payloads as application/graphql. I'll look to migrate the payload serialization to conform with application/graphql+json to hopefully have compatibility between RSocket-JS GraphQL clients and Spring GraphQL.

rstoyanchev added a commit that referenced this issue Mar 28, 2022
rstoyanchev added a commit that referenced this issue Mar 28, 2022
Now that RSocket is included, it's more obvious it's not just web.

See gh-339
rstoyanchev added a commit that referenced this issue Mar 28, 2022
Those are specific to the GraphQL over WebSocket protocol.

See gh-339
rstoyanchev added a commit that referenced this issue Mar 28, 2022
@rstoyanchev
Copy link
Contributor Author

rstoyanchev commented Mar 28, 2022

@linux-china the only thing I see metadata being useful for in this case is to pass a route that maps to the GraphQL handler on the server side. The mime types "application/graphql+json" or "application/json" are for the data payload that carry the GraphQL requests and responses.

@viglucci awesome to hear this is progressing on the RSocket-JS side in parallel. Yes this should work but it would be great to try out. RSocket support is now available in spring-graphql snapshots but we'll also need a Boot snapshot due to the renaming of the web package to server. We're considering some further changes in the Boot GraphQL starter for this with spring-projects/spring-boot#30453. I'll update this issue when it's a good time to try.

Overall, yes it should work given that Spring's Jackson2JsonDecoder is configured to support application/*+json and will get involved for any JSON mime type, including application/graphql+json.

@linux-china
Copy link

linux-china commented Mar 28, 2022

Routing key is still very important for multi GraphQL instances, for example userGraphql to GraphQL User Schema instance, and itemGraphql to GraphQL Item Schema instance, and I have built a small GraphQL gateway, such as user.api.example.com/graphql, item.api.example.com/graphql, and they are all based on routing key.

application/graphql+json is import for GraphQL service call auto detection without routing key. For most Spring GraphQL applications, only one GraphQL instance, why add extra routing key or default graphql routing key? RSocket composite metadata may be hard to some developers, but in this case, just 2 bytes for composite metadata to call GraphQL service: |1| 0x7A + |1| 0x2B if application/graphql+json is 0x2B. It's easy for GraphQL RSocket client to call remote GraphQL RSocket Services.

Above just my opinions.

@rstoyanchev
Copy link
Contributor Author

I'm not sure I follow what you're saying, why would the GraphQL request be in the metadata?

@linux-china
Copy link

linux-china commented Mar 28, 2022

To a GraphQL request, GraphQL json document is RSocket Payload's data, and RSocket Payload's composite metadata contains Routing Metadata and Metadata for data MIME Type . For Metadata Payload for data MIME Type, its value is application/graphql+json

Routing is useful for multi multi GraphQL instances, and message/x.rsocket.mime-type.v0 metadata value indicates this is GraphQL request. If routing is absent in composite metadata, and GraphQL server can handle GraphQL request by message/x.rsocket.mime-type.v0 metadata. I'm not sure it's good or not without routing, just for discussion.

rstoyanchev added a commit that referenced this issue Mar 29, 2022
@rstoyanchev
Copy link
Contributor Author

Thanks for clarifying. I got it now. We don't support the per-stream mime type extension yet in spring-messaging but we'll consider it then.

@rstoyanchev
Copy link
Contributor Author

Closing this since the Spring GraphQL side is now in place, but will provide an update once the Boot starter catches up.

@bclozel
Copy link
Member

bclozel commented May 4, 2022

@viglucci FYI, as of #375 we're now back to using "application/json" as the default media type for servers and clients. "application/graphql+json" is still supported, but we're aligning here with the rest of the industry which does not support the GraphQL over HTTP spec.

@viglucci
Copy link

viglucci commented May 4, 2022

@bclozel thanks for the headsup. I'll align the Apollo integration in rsocket-js with this as well and test against the latest Spring RC once they are available.

@bclozel
Copy link
Member

bclozel commented May 4, 2022

@viglucci we're applying this change quite late in the cycle, next stop is 1.0 scheduled on May 17.

@linux-china
Copy link

linux-china commented May 5, 2022

@viglucci I made a demo to test Spring GraphQL over RSocket, and it works.

import {ApolloClient, ApolloLink, gql, InMemoryCache} from "@apollo/client";
import {TcpClientTransport} from "rsocket-tcp-client";
import {makeRSocketLink} from 'rsocket-graphql-apollo-link'
import {RSocketConnector} from "rsocket-core";


async function rsocketApolloLink(): Promise<ApolloLink> {
    let connector = new RSocketConnector({
        transport: new TcpClientTransport({
            connectionOptions: {
                host: "127.0.0.1",
                port: 42252,
            },
        }),
        setup: {
            dataMimeType: "application/json",
            metadataMimeType: "message/x.rsocket.composite-metadata.v0"
        }
    });
    const rsocketClient = await connector.connect();
    // @ts-ignore
    return makeRSocketLink({
        rsocket: rsocketClient,
        route: "graphql"
    }) as ApolloLink
}

async function helloQuery() {
    const rsocketLink = await rsocketApolloLink();
    const client = new ApolloClient({
        cache: new InMemoryCache(),
        link: rsocketLink
    });

    return await client.query({
        query: gql`query { bookById(id: "book-1") { id name} }`
    });
}

helloQuery().then(result => console.log(result));

@viglucci
Copy link

viglucci commented May 5, 2022

@linux-china awesome!

I had done my own experiments recently as well (with similar results):

@rstoyanchev
Copy link
Contributor Author

@linux-china and @viglucci, thanks for the snippet and links, and @viglucci for making this possible on the RSocket-JS side!

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

No branches or pull requests

4 participants