-
Notifications
You must be signed in to change notification settings - Fork 840
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 #49
Comments
I would love to see this get into As always, PRs are most welcomed 😃 |
To define a schema, err := graphql.NewSchema(graphql.SchemaConfig{
Query: ...,
Mutation: ...,
Subscription: graphql.NewObject(graphql.ObjectConfig{
Name: "SubscriptionRoot",
Fields: graphql.Fields{
"subscribeUser": &graphql.Field{
Type: graphql.String,
},
},
}),
}) To make subscription query: query Foo {
...
}
mutation Bar {
...
}
subscription Baz {
subscribeUser
} Closing this. Cheers! |
Any Working Example ? |
I'd also love to see an example. @nikhilmahesh did you get anything? |
@truongsinh I saw this, but it returns rand int ...etc. I'd like to see real world example. Which I believe i found in the testing steps here; https://github.com/graphql-go/graphql/blob/master/kitchen-sink.graphql#L32 The truth is I don't know exactly how subscriptions are suppose to work, and seeing server side and front-end would be extremely helpful. |
I guess there is no any full-featured example. Moreover, the example will depend on a websockets lib and client code etc. |
Has a full-featured example been published recently? |
Any activity here, or any discussions around making a full fledged subscription support like apollo-subscription-server in go graphql server? Really interested in contributing too if needed. Or else if functionality is there, can help setup a example implementation. |
Some might be interested: We've just released https://github.com/functionalfoundry/graphqlws/, which allows implement subscription-enabled GraphQL servers that work with typical clients (like Apollo) out of the box. It integrates seamlessly with I still need to create a useful example. The easiest way to connect it to a database is to have a pubsub mechanism to listen for database changes and whenever something changes, you identify which subscriptions may be affected (e.g. by looking at their top-level query fields), re-run their queries and push data out to them (this is for instance what Graphcool does). I've described the library a bit here: https://medium.com/functional-foundry/building-graphql-servers-with-subscriptions-in-go-2a60f11dc9f5 Hope this helps! |
This approach is pretty inefficient and just won't scale imho. Most DBs have triggers at a table or bucket lever. But how can you work out which query maps to that query ? If a mutation comes in and it's event fires various reactions then you can maybe isolate which data is changed in the middle tier and return only the resulting delta. Another easy way is for all viewmodel ( which is basically what a query maps to ) to hold a sequence I'd from a materialised view in the DB. The other way is to just hold a sequence number per query and don't hold any data in the db except the sequence ID. Then when a mutation results in a new materialised view record just show it on the queue. |
Hi @Dverlik, thanks for reaching us for a question, actually package main
import (
"encoding/json"
"log"
"net/http"
"sync"
"time"
"github.com/gorilla/websocket"
"github.com/graphql-go/graphql"
"github.com/graphql-go/handler"
)
type Post struct {
ID int `json:"id"`
Likes int `json:"count"`
}
type ConnectionACKMessage struct {
OperationID string `json:"id,omitempty"`
Type string `json:"type"`
Payload struct {
Query string `json:"query"`
} `json:"payload,omitempty"`
}
var PostType = graphql.NewObject(graphql.ObjectConfig{
Name: "Post",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.Int,
},
"likes": &graphql.Field{
Type: graphql.Int,
},
},
})
type Subscriber struct {
ID int
Conn *websocket.Conn
RequestString string
OperationID string
}
func main() {
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
Subprotocols: []string{"graphql-ws"},
}
var posts = []*Post{
&Post{ID: 1, Likes: 1},
&Post{ID: 2, Likes: 2},
}
var subscribers sync.Map
schema, err := graphql.NewSchema(graphql.SchemaConfig{
Query: graphql.NewObject(graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"posts": &graphql.Field{
Type: graphql.NewList(PostType),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return posts, nil
},
},
},
}),
Subscription: graphql.NewObject(graphql.ObjectConfig{
Name: "Subscription",
Fields: graphql.Fields{
"postLikesSubscribe": &graphql.Field{
Type: graphql.NewList(PostType),
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return posts, nil
},
},
},
}),
})
if err != nil {
log.Fatal(err)
}
h := handler.New(&handler.Config{
Schema: &schema,
Pretty: true,
GraphiQL: false,
Playground: true,
})
http.Handle("/graphql", h)
http.HandleFunc("/subscriptions", func(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("failed to do websocket upgrade: %v", err)
return
}
connectionACK, err := json.Marshal(map[string]string{
"type": "connection_ack",
})
if err != nil {
log.Printf("failed to marshal ws connection ack: %v", err)
}
if err := conn.WriteMessage(websocket.TextMessage, connectionACK); err != nil {
log.Printf("failed to write to ws connection: %v", err)
return
}
go func() {
for {
_, p, err := conn.ReadMessage()
if websocket.IsCloseError(err, websocket.CloseGoingAway) {
return
}
if err != nil {
log.Println("failed to read websocket message: %v", err)
return
}
var msg ConnectionACKMessage
if err := json.Unmarshal(p, &msg); err != nil {
log.Printf("failed to unmarshal: %v", err)
return
}
if msg.Type == "start" {
length := 0
subscribers.Range(func(key, value interface{}) bool {
length++
return true
})
var subscriber = Subscriber{
ID: length + 1,
Conn: conn,
RequestString: msg.Payload.Query,
OperationID: msg.OperationID,
}
subscribers.Store(subscriber.ID, &subscriber)
}
}
}()
})
go func() {
for {
time.Sleep(1 * time.Second)
for _, post := range posts {
post.Likes = post.Likes + 1
}
subscribers.Range(func(key, value interface{}) bool {
subscriber, ok := value.(*Subscriber)
if !ok {
return true
}
payload := graphql.Do(graphql.Params{
Schema: schema,
RequestString: subscriber.RequestString,
})
message, err := json.Marshal(map[string]interface{}{
"type": "data",
"id": subscriber.OperationID,
"payload": payload,
})
if err != nil {
log.Printf("failed to marshal message: %v", err)
return true
}
if err := subscriber.Conn.WriteMessage(websocket.TextMessage, message); err != nil {
if err == websocket.ErrCloseSent {
subscribers.Delete(key)
return true
}
log.Printf("failed to write to ws connection: %v", err)
return true
}
return true
})
}
}()
log.Printf("server running on port :8080")
http.ListenAndServe(":8080", nil)
} |
Subscription support: graphql-go/graphql#49 (comment) Dataloading support: graphql-go/graphql#389 Concurrency support: graphql-go/graphql#389
- Subscription support: graphql-go/graphql#49 (comment) - Concurrency support: graphql-go/graphql#389 - Dataloading support: graphql-go/graphql#388
@chris-ramon |
I am trying to solve this on the handler library |
While this kind of subscription is nice for repeating same requests over and over, it is not simplifying much async communication within single request, requiring external ways of actually keeping the state of specific connection. https://github.com/functionalfoundry/graphqlws mitigates that to some degree, but have quite concealed and non-extensible implementation. So in my actual project I had to do my own implementation, which lead to my https://github.com/eientei/wsgraphql pet-project, which does basically the same things, but provides a bit more of control and easier way to keep track of state within the same subscription requests (via mutable context instance, which is arguably questionable). But it is still far from ideal, the proper go way of things would be either a |
Is there an efficient way to implement Subscription |
- Subscription support: graphql-go/graphql#49 (comment) - Concurrency support: graphql-go/graphql#389 - Dataloading support: graphql-go/graphql#388
Some initial work has been done to implement subscriptions in graphql-js: graphql/graphql-js#189
Here's the relay discussion for getting support into relay: facebook/relay#541
Would love to see this subscriptions land here once they are in the reference implementation 😄
The text was updated successfully, but these errors were encountered: