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

Apollo Federation Spec: Fetch service capabilities #507

Merged
merged 5 commits into from
Mar 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions example/apollo_federation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Apollo Federation

A simple example of integration with apollo federation as subgraph. Tested with Go v1.18, Node.js v16.14.2 and yarn 1.22.18.

To run this server

`go run ./example/apollo_federation/subgraph_one/server.go`

`go run ./example/apollo_federation/subgraph_two/server.go`

`cd example/apollo_federation/gateway`

`yarn start`

and go to localhost:4000 to interact

Execute the query:

```
query {
hello
hi
}
```

and you should see a result similar to this:

```json
{
"data": {
"hello": "Hello from subgraph one!",
"hi": "Hi from subgraph two!"
}
}
```
1 change: 1 addition & 0 deletions example/apollo_federation/gateway/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/node_modules
20 changes: 20 additions & 0 deletions example/apollo_federation/gateway/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const { ApolloServer } = require('apollo-server')
const { ApolloGateway, IntrospectAndCompose } = require('@apollo/gateway');

const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'one', url: 'http://localhost:4001/query' },
{ name: 'two', url: 'http://localhost:4002/query' },
],
}),
});

const server = new ApolloServer({
gateway,
subscriptions: false,
});

server.listen().then(({ url }) => {
console.log(`Server ready at ${url}`);
});
14 changes: 14 additions & 0 deletions example/apollo_federation/gateway/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "apollo-federation-gateway",
"version": "1.0.0",
"description": "Graphql Federation",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"@apollo/gateway": "^0.49.0",
"apollo-server": "^2.21.1",
"graphql": "^15.5.0"
}
}
34 changes: 34 additions & 0 deletions example/apollo_federation/subgraph_one/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import (
"log"
"net/http"

"github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
)

var schema = `
schema {
query: Query
}

type Query {
hello: String!
}
`

type resolver struct{}

func (r *resolver) Hello() string {
return "Hello from subgraph one!"
}

func main() {
opts := []graphql.SchemaOpt{graphql.UseFieldResolvers(), graphql.MaxParallelism(20)}
schema := graphql.MustParseSchema(schema, &resolver{}, opts...)

http.Handle("/query", &relay.Handler{Schema: schema})

log.Fatal(http.ListenAndServe(":4001", nil))
}
34 changes: 34 additions & 0 deletions example/apollo_federation/subgraph_two/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import (
"log"
"net/http"

"github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
)

var schema = `
schema {
query: Query
}

type Query {
hi: String!
}
`

type resolver struct{}

func (r *resolver) Hi() string {
return "Hi from subgraph two!"
}

func main() {
opts := []graphql.SchemaOpt{graphql.UseFieldResolvers(), graphql.MaxParallelism(20)}
schema := graphql.MustParseSchema(schema, &resolver{}, opts...)

http.Handle("/query", &relay.Handler{Schema: schema})

log.Fatal(http.ListenAndServe(":4002", nil))
}
27 changes: 27 additions & 0 deletions example/social/introspect.json
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,33 @@
"name": "User",
"possibleTypes": null
},
{
"description": null,
"enumValues": null,
"fields": [
{
"args": [],
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "sdl",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
],
"inputFields": null,
"interfaces": [],
"kind": "OBJECT",
"name": "_Service",
"possibleTypes": null
},
{
"description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior\nin ways field arguments will not suffice, such as conditionally including or\nskipping a field. Directives provide this by describing additional information\nto the executor.",
"enumValues": null,
Expand Down
27 changes: 27 additions & 0 deletions example/starwars/introspect.json
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,33 @@
"name": "String",
"possibleTypes": null
},
{
"description": null,
"enumValues": null,
"fields": [
{
"args": [],
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "sdl",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
],
"inputFields": null,
"interfaces": [],
"kind": "OBJECT",
"name": "_Service",
"possibleTypes": null
},
{
"description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior\nin ways field arguments will not suffice, such as conditionally including or\nskipping a field. Directives provide this by describing additional information\nto the executor.",
"enumValues": null,
Expand Down
34 changes: 34 additions & 0 deletions graphql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2144,6 +2144,7 @@ func TestIntrospection(t *testing.T) {
{ "name": "SearchResult" },
{ "name": "Starship" },
{ "name": "String" },
{ "name": "_Service" },
{ "name": "__Directive" },
{ "name": "__DirectiveLocation" },
{ "name": "__EnumValue" },
Expand Down Expand Up @@ -4326,3 +4327,36 @@ func TestCircularFragmentMaxDepth(t *testing.T) {
},
})
}

func TestQueryService(t *testing.T) {
t.Parallel()

schemaString := `
schema {
query: Query
}

type Query {
hello: String!
}`

gqltesting.RunTests(t, []*gqltesting.Test{
{
Schema: graphql.MustParseSchema(schemaString, &helloWorldResolver1{}),
Query: `
{
_service{
sdl
}
}
`,
ExpectedResult: fmt.Sprintf(`
{
"_service": {
"sdl": "\n\tschema {\n\t\tquery: Query\n\t}\n\n\ttype Query {\n\t\thello: String!\n\t}"
}
}
`),
},
})
}
18 changes: 18 additions & 0 deletions internal/exec/resolvable/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ type Meta struct {
FieldSchema Field
FieldType Field
FieldTypename Field
FieldService Field
Schema *Object
Type *Object
Service *Object
}

func newMeta(s *types.Schema) *Meta {
Expand All @@ -32,6 +34,12 @@ func newMeta(s *types.Schema) *Meta {
panic(err)
}

metaService := s.Types["_Service"].(*types.ObjectTypeDefinition)
sv, err := b.makeObjectExec(metaService.Name, metaService.Fields, nil, false, reflect.TypeOf(&introspection.Service{}))
if err != nil {
panic(err)
}

if err := b.finish(); err != nil {
panic(err)
}
Expand Down Expand Up @@ -60,11 +68,21 @@ func newMeta(s *types.Schema) *Meta {
TraceLabel: "GraphQL field: __type",
}

fieldService := Field{
FieldDefinition: types.FieldDefinition{
Name: "_service",
Type: s.Types["_Service"],
},
TraceLabel: "GraphQL field: _service",
}

return &Meta{
FieldSchema: fieldSchema,
FieldTypename: fieldTypename,
FieldType: fieldType,
FieldService: fieldService,
Schema: so,
Type: t,
Service: sv,
}
}
11 changes: 11 additions & 0 deletions internal/exec/selected/selected.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,17 @@ func applySelectionSet(r *Request, s *resolvable.Schema, e *resolvable.Object, s
})
}

case "_service":
if !r.DisableIntrospection {
flattenedSels = append(flattenedSels, &SchemaField{
Field: s.Meta.FieldService,
Alias: field.Alias.Name,
Sels: applySelectionSet(r, s, s.Meta.Service, field.SelectionSet),
Async: true,
FixedResult: reflect.ValueOf(introspection.WrapService(r.Schema)),
})
}

default:
fe := e.Fields[field.Name.Name]

Expand Down
4 changes: 4 additions & 0 deletions internal/schema/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,8 @@ var metaSrc = `
# Indicates this type is a non-null. ` + "`" + `ofType` + "`" + ` is a valid field.
NON_NULL
}

type _Service {
sdl: String!
}
`
2 changes: 2 additions & 0 deletions internal/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ func Parse(s *types.Schema, schemaString string, useStringDescriptions bool) err
}
}

s.SchemaString = schemaString

return nil
}

Expand Down
5 changes: 5 additions & 0 deletions internal/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,11 @@ func validateSelection(c *opContext, sel types.Selection, t types.NamedType) {
},
Type: c.schema.Types["__Type"],
}
case "_service":
f = &types.FieldDefinition{
Name: "_service",
Type: c.schema.Types["_Service"],
}
default:
f = fields(t).Get(fieldName)
if f == nil && t != nil {
Expand Down
13 changes: 13 additions & 0 deletions introspection/introspection.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,16 @@ func (r *Directive) Args() []*InputValue {
}
return l
}

type Service struct {
schema *types.Schema
}

// WrapService is only used internally.
func WrapService(schema *types.Schema) *Service {
return &Service{schema}
}

func (r *Service) SDL() string {
return r.schema.SchemaString
}
1 change: 1 addition & 0 deletions types/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type Schema struct {
Unions []*Union
Enums []*EnumTypeDefinition
Extensions []*Extension
SchemaString string
}

func (s *Schema) Resolve(name string) Type {
Expand Down