Skip to content

Commit

Permalink
Merge pull request #1018 from 99designs/federation-docs
Browse files Browse the repository at this point in the history
Federation docs and examples
  • Loading branch information
vektah authored Feb 8, 2020
2 parents 656a07d + 3045b2c commit e289aaa
Show file tree
Hide file tree
Showing 45 changed files with 10,000 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .circleci/check-coverage
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
set -euo pipefail
go get github.com/mattn/goveralls

go test -coverprofile=/tmp/coverage.out -coverpkg=./... $(go list github.com/99designs/gqlgen/... | grep -v server)
go test -coverprofile=/tmp/coverage.out -coverpkg=./... $(go list github.com/99designs/gqlgen/... | grep -v example)
goveralls -coverprofile=/tmp/coverage.out -service=circle-ci -repotoken=$REPOTOKEN -ignore='example/*/*,example/*/*/*,integration/*,integration/*/*,codegen/testserver/*'
13 changes: 13 additions & 0 deletions .circleci/check-federation
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash

set -euo pipefail

cd example/federation

./start.sh &

sleep 10

echo "### running jest integration spec"
./node_modules/.bin/jest --color

11 changes: 11 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ jobs:
- run: cd integration ; npm install
- run: .circleci/check-integration

federation:
docker:
- image: alpine:3.10
steps:
- checkout
- run: apk add --no-cache --no-progress nodejs npm go musl-dev git bash
- run: go mod download
- run: cd example/federation ; npm install
- run: .circleci/check-federation

workflows:
version: 2
build_and_test:
Expand All @@ -44,4 +54,5 @@ workflows:
- test
- cover
- integration
- federation

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
/integration/node_modules
/integration/schema-fetched.graphql
/example/chat/package-lock.json
/example/federation/package-lock.json
/example/federation/node_modules
/codegen/gen
/gen

Expand Down
187 changes: 187 additions & 0 deletions docs/content/recipes/federation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
---
title: 'Using Apollo federation gqlgen'
description: How federate many services into a single graph using Apollo
linkTitle: Apollo Federation
menu: { main: { parent: 'recipes' } }
---

In this quick guide we are going to implement the example [Apollo Federation](https://www.apollographql.com/docs/apollo-server/federation/introduction/)
server in gqlgen. You can find the finished result in the [examples directory](/example/federation).


## Create the federated servers

For each server to be federated we will create a new gqlgen project.

```bash
go run github.com/99designs/gqlgen
```

Update the schema to reflect the federated example
```graphql
type Review {
body: String
author: User @provides(fields: "username")
product: Product
}

type User @extends @key(fields: "id") {
id: ID! @external
reviews: [Review]
}

type Product @extends @key(fields: "upc") {
upc: String! @external
reviews: [Review]
}
```

> Note
>
> gqlgen doesnt currently support `extend type Foo` syntax for apollo federation, we must use
> the `@extends` directive instead.


and regenerate
```bash
go run github.com/99designs/gqlgen
```

then implement the resolvers
```go
// These two methods are required for gqlgen to resolve the internal id-only wrapper structs.
// This boilerplate might be removed in a future version of gqlgen that can no-op id only nodes.
func (r *entityResolver) FindProductByUpc(ctx context.Context, upc string) (*model.Product, error) {
return &model.Product{
Upc: upc,
}, nil
}

func (r *entityResolver) FindUserByID(ctx context.Context, id string) (*model.User, error) {
return &model.User{
ID: id,
}, nil
}

// Here we implement the stitched part of this service, returning reviews for a product. Of course normally you would
// go back to the database, but we are just making some data up here.
func (r *productResolver) Reviews(ctx context.Context, obj *model.Product) ([]*model.Review, error) {
switch obj.Upc {
case "top-1":
return []*model.Review{{
Body: "A highly effective form of birth control.",
}}, nil

case "top-2":
return []*model.Review{{
Body: "Fedoras are one of the most fashionable hats around and can look great with a variety of outfits.",
}}, nil

case "top-3":
return []*model.Review{{
Body: "This is the last straw. Hat you will wear. 11/10",
}}, nil

}
return nil, nil
}

func (r *userResolver) Reviews(ctx context.Context, obj *model.User) ([]*model.Review, error) {
if obj.ID == "1234" {
return []*model.Review{{
Body: "Has an odd fascination with hats.",
}}, nil
}
return nil, nil
}
```

> Note
>
> Repeat this step for each of the services in the apollo doc (accounts, products, reviews)
## Create the federation gateway

```bash
npm install --save @apollo/gateway apollo-server graphql
```

```typescript
const { ApolloServer } = require('apollo-server');
const { ApolloGateway } = require("@apollo/gateway");

const gateway = new ApolloGateway({
serviceList: [
{ name: 'accounts', url: 'http://localhost:4001/query' },
{ name: 'products', url: 'http://localhost:4002/query' },
{ name: 'reviews', url: 'http://localhost:4003/query' }
],
});

const server = new ApolloServer({
gateway,

subscriptions: false,
});

server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
```

## Start all the services

In separate terminals:
```bash
go run accounts/server.go
go run products/server.go
go run reviews/server.go
node gateway/index.js
```

## Query the federated gateway

The examples from the apollo doc should all work, eg

```graphql
query {
me {
username
reviews {
body
product {
name
upc
}
}
}
}
```

should return

```json
{
"data": {
"me": {
"username": "Me",
"reviews": [
{
"body": "A highly effective form of birth control.",
"product": {
"name": "Trilby",
"upc": "top-1"
}
},
{
"body": "Fedoras are one of the most fashionable hats around and can look great with a variety of outfits.",
"product": {
"name": "Trilby",
"upc": "top-1"
}
}
]
}
}
}
```
55 changes: 55 additions & 0 deletions example/federation/accounts/gqlgen.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Where are all the schema files located? globs are supported eg src/**/*.graphqls
schema:
- graph/*.graphqls

# Where should the generated server code go?
exec:
filename: graph/generated/generated.go
package: generated

federation:
filename: graph/generated/federation.go
package: generated

# Where should any generated models go?
model:
filename: graph/model/models_gen.go
package: model

# Where should the resolver implementations go?
resolver:
layout: follow-schema
dir: graph
package: graph

# Optional: turn on use `gqlgen:"fieldName"` tags in your models
# struct_tag: json

# Optional: turn on to use []Thing instead of []*Thing
# omit_slice_element_pointers: false

# Optional: set to speed up generation time by not performing a final validation pass.
# skip_validation: true

# gqlgen will search for any type names in the schema in these go packages
# if they match it will use them, otherwise it will generate them.
autobind:
- "github.com/99designs/gqlgen/example/federation/accounts/graph/model"

# This section declares type mapping between the GraphQL and go type systems
#
# The first line in each type will be used as defaults for resolver arguments and
# modelgen, the others will be allowed when binding to fields. Configure them to
# your liking
models:
ID:
model:
- github.com/99designs/gqlgen/graphql.ID
- github.com/99designs/gqlgen/graphql.Int
- github.com/99designs/gqlgen/graphql.Int64
- github.com/99designs/gqlgen/graphql.Int32
Int:
model:
- github.com/99designs/gqlgen/graphql.Int
- github.com/99designs/gqlgen/graphql.Int64
- github.com/99designs/gqlgen/graphql.Int32
26 changes: 26 additions & 0 deletions example/federation/accounts/graph/entity.resolvers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
package graph

import (
"context"

"github.com/99designs/gqlgen/example/federation/accounts/graph/generated"
"github.com/99designs/gqlgen/example/federation/accounts/graph/model"
)

func (r *entityResolver) FindUserByID(ctx context.Context, id string) (*model.User, error) {
name := "User " + id
if id == "1234" {
name = "Me"
}

return &model.User{
ID: id,
Username: name,
}, nil
}

func (r *Resolver) Entity() generated.EntityResolver { return &entityResolver{r} }

type entityResolver struct{ *Resolver }
57 changes: 57 additions & 0 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.

Loading

0 comments on commit e289aaa

Please sign in to comment.