Skip to content

Commit

Permalink
Resolve requests for federation entities in parallel
Browse files Browse the repository at this point in the history
In apollo federation, we may be asked for data about a list of entities.
These can typically be resolved in parallel, just as with sibling fields
in ordinary GraphQL queries.  Now we do!

Note that I kept the behavior where if one lookup fails, we cancel the
whole assembly.  It's not clear to me if that's the ideal behavior --
there are arguments both ways -- but I figured it was easiest to retain
it for now.  (I used x/sync/errgroup to do so; it's already a transitive
dep via x/tools but if the new direct dep is an issue I can do it with
sync.Waitgroup instead, at the cost of some extra code.)

The examples probably give the clearest picture of the changes. (And the
clearest test; the changed functionality is already exercised by
`integration-test.js` as watching the test server logs will attest.)

Fixes 99designs#1278.
  • Loading branch information
benjaminjkraft authored and csilvers committed Oct 14, 2020
1 parent 07c1f93 commit 681f94b
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 36 deletions.
38 changes: 30 additions & 8 deletions example/federation/accounts/graph/generated/federation.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 30 additions & 8 deletions example/federation/products/graph/generated/federation.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 34 additions & 11 deletions example/federation/reviews/graph/generated/federation.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ require (
github.com/urfave/cli/v2 v2.1.1
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e
github.com/vektah/gqlparser/v2 v2.1.0
golang.org/x/sync v0.0.0-20190423024810-112230192c58
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589
gopkg.in/yaml.v2 v2.2.4
sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,9 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
40 changes: 31 additions & 9 deletions plugin/federation/federation.gotpl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
{{ reserveImport "fmt" }}
{{ reserveImport "strings" }}

{{ reserveImport "golang.org/x/sync/errgroup" }}
{{ reserveImport "github.com/99designs/gqlgen/plugin/federation/fedruntime" }}

func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.Service, error) {
Expand All @@ -26,11 +27,11 @@ func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.

{{if .Entities}}
func (ec *executionContext) __resolve_entities(ctx context.Context, representations []map[string]interface{}) ([]fedruntime.Entity, error) {
list := []fedruntime.Entity{}
for _, rep := range representations {
list := make([]fedruntime.Entity, len(representations))
resolveEntity := func(ctx context.Context, i int, rep map[string]interface{}) error {
typeName, ok := rep["__typename"].(string)
if !ok {
return nil, errors.New("__typename must be an existing string")
return errors.New("__typename must be an existing string")
}
switch typeName {
{{ range .Entities }}
Expand All @@ -39,31 +40,52 @@ func (ec *executionContext) __resolve_entities(ctx context.Context, representati
{{ range $i, $keyField := .KeyFields -}}
id{{$i}}, err := ec.{{.TypeReference.UnmarshalFunc}}(ctx, rep["{{$keyField.Field.Name}}"])
if err != nil {
return nil, errors.New(fmt.Sprintf("Field %s undefined in schema.", "{{$keyField.Field.Name}}"))
return errors.New(fmt.Sprintf("Field %s undefined in schema.", "{{$keyField.Field.Name}}"))
}
{{end}}

entity, err := ec.resolvers.Entity().{{.ResolverName | go}}(ctx,
{{ range $i, $_ := .KeyFields -}} id{{$i}}, {{end}})
if err != nil {
return nil, err
return err
}

{{ range .Requires }}
{{ range .Fields}}
entity.{{.NameGo}}, err = ec.{{.TypeReference.UnmarshalFunc}}(ctx, rep["{{.Name}}"])
if err != nil {
return nil, err
return err
}
{{ end }}
{{ end }}
list = append(list, entity)
list[i] = entity
return nil
{{ end }}
{{ end }}
default:
return nil, errors.New("unknown type: "+typeName)
return errors.New("unknown type: "+typeName)
}
}
return list, nil

// if there are multiple entities to resolve, parallelize (similar to
// graphql.FieldSet.Dispatch)
switch len(representations) {
case 0:
return list, nil
case 1:
err := resolveEntity(ctx, 0, representations[0])
return list, err
default:
eg, gCtx := errgroup.WithContext(ctx)
for i, rep := range representations {
i, rep := i, rep
eg.Go(func() error { return resolveEntity(gCtx, i, rep) })
}
err := eg.Wait()
if err != nil {
return nil, err
}
return list, nil
}
}
{{end}}

0 comments on commit 681f94b

Please sign in to comment.