在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称(OpenSource Name):samsarahq/thunder开源软件地址(OpenSource Url):https://github.com/samsarahq/thunder开源编程语言(OpenSource Language):JavaScript 65.7%开源软件介绍(OpenSource Introduction):Thunder is a Go framework for rapidly building powerful graphql servers. Thunder has support for schemas automatically generated from Go types, live queries, query batching, and more. Thunder is an open-source project from Samsara. Feature Lightning TourThunder has a number of features to make it easy to build sophisticated schemas. This section provides a brief overview of some of them. Reflection-based schema buildingThunder generates resolvers automatically from Go struct types and function
definitions. For example, the // Friend is a small struct representing a person.
type Friend struct {
FirstName string
Last string `graphql:"lastName"` // use a custom name
Added time.Date `graphql:"-"` // don't expose over graphql
}
// FullName builds a friend's full name.
func (f *Friend) FullName() string {
return fmt.Sprintf("%s %s", f.FirstName, f.Last)
}
// registerFriend registers custom resolvers on the Friend type.
//
// Note: registerFriend wouldn't be necessary if the type only
// had the default struct field resolvers above.
func registerFriend(schema *schemabuilder.Schema) {
object := schema.Object("Friend", Friend{})
// fullName is a computed field on the Friend{} object.
object.FieldFunc("fullName", Friend.FullName)
} PaginationLive queriesThunder has support for automatically updating queries using resolver invalidation. With invalidation, code on the server can trigger updates on the client using a persistent WebSocket connection. The simplest example is a clock that updates over time. Every 10 seconds the
// registerQuery registers the resolvers on the core graphql query type.
func registerQuery(schema *schemabuilder.Schema) {
query := schema.Query()
// time returns the current time.
query.FieldFunc("time", func(ctx context.Context) string {
// Invalidate the result of this resolver after 10 seconds.
reactive.InvalidateAfter(ctx, 10 * time.Second)
// Return the current time. Will be re-executed automatically.
return time.Now().String()
})
} Using Thunder's lightweight // A Post holds a row from the MySQL posts table.
type Post struct {
Id int64 `sqlgen:",primary"`
Title string
}
// Server implements a graphql server. It has persistent handles to eg. the
// database.
type Server struct {
db *livesql.LiveDB
}
// registerQuery registers the root query resolvers.
func (s *Server) registerQuery(schema *schemabuilder.Schema) {
query := schema.Query()
// posts returns all posts in the database.
query.FieldFunc("posts", func(ctx context.Context) ([]*Post, error) {
var posts []*Post
if err := s.db.Query(ctx, &posts, nil, nil); err != nil {
return nil, err
}
return posts, nil
})
} Built-in parallel execution and batchingThunder automatically runs independent resolvers in different goroutines to
quickly compute complex queries. To keep large queries efficient, Thunder has
support for built-in batching similar to Facebook's Batching is very useful when fetching related objects from a SQL database. Thunder's
type Post struct {
Id int64 `sqlgen:",primary"`
Title string
AuthorId int64
}
// An Author represents a row in the authors table.
type Author struct {
Id int64 `sqlgen:",primary"`
Name string
}
// registerPost registers resolvers on the Post type.
func (s *Server) registerPost(schema *schemabuilder.Schema) {
object := schema.Object("post", Post{})
// author return the Author object corresponding to a Post's AuthorId.
object.FieldFunc("author", func(ctx context.Context, p *Post) (*Author, error) {
var author *Author
if err := s.db.QueryRow(ctx, &author, sqlgen.Filter{"id": p.AuthorId}, nil); err != nil {
return nil, err
}
return author, nil
})
} To execute the query query PostsWithAuthors {
posts {
title
author { name }
}
} Thunder will execute Built-in graphiqlTo get started quickly without wrangling any JavaScript, Thunder comes with
a built-in // Expose schema and graphiql.
http.Handle("/graphql", graphql.Handler(schema))
http.Handle("/graphiql/", http.StripPrefix("/graphiql/", graphiql.Handler()))
http.ListenAndServe(":3030", nil) Split schema building for large graphql serversA large GraphQL server might have many resolvers on some shared types. To
keep packages reasonably-sized, Thunder's schema builder supports extending a
schema. For example, if you have a package common
type User struct {}
package photos
type PhotosServer {}
func (s *PhotosServer) registerUser(schema *schemabuilder.Schema) {
object := schema.Object("User", common.User{})
object.FieldFunc("photos", s.fetchUserPhotos)
}
package events
type EventsServer {}
func (s *EventsServer) registerUser(schema *schemabuilder.Schema) {
object := schema.Object("User", common.User{})
object.FieldFunc("events", s.fetchUserEvents)
} Getting started
A minimal complete serverThe program below is a fully-functional graphql server written using Thunder. It
does not use package main
import (
"context"
"net/http"
"time"
"github.com/samsarahq/thunder/graphql"
"github.com/samsarahq/thunder/graphql/graphiql"
"github.com/samsarahq/thunder/graphql/introspection"
"github.com/samsarahq/thunder/graphql/schemabuilder"
"github.com/samsarahq/thunder/reactive"
)
type post struct {
Title string
Body string
CreatedAt time.Time
}
// server is our graphql server.
type server struct {
posts []post
}
// registerQuery registers the root query type.
func (s *server) registerQuery(schema *schemabuilder.Schema) {
obj := schema.Query()
obj.FieldFunc("posts", func() []post {
return s.posts
})
}
// registerMutation registers the root mutation type.
func (s *server) registerMutation(schema *schemabuilder.Schema) {
obj := schema.Mutation()
obj.FieldFunc("echo", func(args struct{ Message string }) string {
return args.Message
})
}
// registerPost registers the post type.
func (s *server) registerPost(schema *schemabuilder.Schema) {
obj := schema.Object("Post", post{})
obj.FieldFunc("age", func(ctx context.Context, p *post) string {
reactive.InvalidateAfter(ctx, 5*time.Second)
return time.Since(p.CreatedAt).String()
})
}
// schema builds the graphql schema.
func (s *server) schema() *graphql.Schema {
builder := schemabuilder.NewSchema()
s.registerQuery(builder)
s.registerMutation(builder)
s.registerPost(builder)
return builder.MustBuild()
}
func main() {
// Instantiate a server, build a server, and serve the schema on port 3030.
server := &server{
posts: []post{
{Title: "first post!", Body: "I was here first!", CreatedAt: time.Now()},
{Title: "graphql", Body: "did you hear about Thunder?", CreatedAt: time.Now()},
},
}
schema := server.schema()
introspection.AddIntrospectionToSchema(schema)
// Expose schema and graphiql.
http.Handle("/graphql", graphql.Handler(schema))
http.Handle("/graphiql/", http.StripPrefix("/graphiql/", graphiql.Handler()))
http.ListenAndServe(":3030", nil)
} Using Thunder without Websockets (POST requests)For use with non-live clients (e.g. Relay, Apollo) thunder provides an HTTP handler that can serve POST requests, instead of having the client connect over a websocket. In this mode, thunder does not provide live query updates. In the above example, the func main() {
// Instantiate a server, build a server, and serve the schema on port 3030.
server := &server{
posts: []post{
{Title: "first post!", Body: "I was here first!", CreatedAt: time.Now()},
{Title: "graphql", Body: "did you hear about Thunder?", CreatedAt: time.Now()},
},
}
schema := server.schema()
introspection.AddIntrospectionToSchema(schema)
// Expose GraphQL POST endpoint.
http.Handle("/graphql", graphql.HTTPHandler(schema))
http.ListenAndServe(":3030", nil)
} Emitting a schema.jsonThunder can emit a GraphQL introspection query schema useful for compatibility with other GraphQL tooling. Alongside code from the above example, here is a small program for registering our schema and writing the JSON output to stdout. // schema_generator.go
func main() {
// Instantiate a server and run the introspection query on it.
server := &server{...}
builderSchema := schemabuilder.NewSchema()
server.registerQuery(builderSchema)
server.registerMutation(builderSchema)
// ...
valueJSON, err := introspection.ComputeSchemaJSON(*builderSchema)
if err != nil {
panic(err)
}
fmt.Print(string(valueJSON))
} This program can then be run to generate $ go run schema_generator.go > schema.json Code organizationThe source code in this repository is organized as follows:
StatusThunder has proven itself in production use at Samsara for close to two years. This repository is still under development, and there will be some breaking changes to the API but they should be manageable. If you're adventurous, please give it a try. |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论