github.com/99designs/gqlgen@v0.17.45/docs/content/recipes/federation.md (about)

     1  ---
     2  title: 'Using Apollo federation gqlgen'
     3  description: How federate many services into a single graph using Apollo
     4  linkTitle: Apollo Federation
     5  menu: { main: { parent: 'recipes' } }
     6  ---
     7  
     8  In this quick guide we are going to implement the example [Apollo Federation](https://www.apollographql.com/docs/apollo-server/federation/introduction/)
     9  server in gqlgen. You can find the finished result in the [examples directory](https://github.com/99designs/gqlgen/tree/master/_examples/federation).
    10  
    11  ## Enable federation
    12  
    13  Uncomment federation configuration in your `gqlgen.yml`
    14  
    15  ```yml
    16  # Uncomment to enable federation
    17  federation:
    18    filename: graph/federation.go
    19    package: graph
    20  ```
    21  
    22  ### Federation 2
    23  
    24  If you are using Apollo's Federation 2 standard, your schema should automatically be upgraded so long as you include the required `@link` directive within your schema. If you want to force Federation 2 composition, the `federation` configuration supports a `version` flag to override that. For example:
    25  
    26  ```yml
    27  federation:
    28    filename: graph/federation.go
    29    package: graph
    30    version: 2
    31  ```
    32  
    33  ## Create the federated servers
    34  
    35  For each server to be federated we will create a new gqlgen project.
    36  
    37  ```bash
    38  go run github.com/99designs/gqlgen
    39  ```
    40  
    41  Update the schema to reflect the federated example
    42  ```graphql
    43  type Review {
    44    body: String
    45    author: User @provides(fields: "username")
    46    product: Product
    47  }
    48  
    49  extend type User @key(fields: "id") {
    50    id: ID! @external # External directive not required for key fields in federation v2
    51    reviews: [Review]
    52  }
    53  
    54  extend type Product @key(fields: "upc") {
    55    upc: String! @external # External directive not required for key fields in federation v2
    56    reviews: [Review]
    57  }
    58  ```
    59  
    60  
    61  and regenerate
    62  ```bash
    63  go run github.com/99designs/gqlgen
    64  ```
    65  
    66  then implement the resolvers
    67  ```go
    68  // These two methods are required for gqlgen to resolve the internal id-only wrapper structs.
    69  // This boilerplate might be removed in a future version of gqlgen that can no-op id only nodes.
    70  func (r *entityResolver) FindProductByUpc(ctx context.Context, upc string) (*model.Product, error) {
    71  	return &model.Product{
    72  		Upc: upc,
    73  	}, nil
    74  }
    75  
    76  func (r *entityResolver) FindUserByID(ctx context.Context, id string) (*model.User, error) {
    77  	return &model.User{
    78  		ID: id,
    79  	}, nil
    80  }
    81  
    82  // Here we implement the stitched part of this service, returning reviews for a product. Of course normally you would
    83  // go back to the database, but we are just making some data up here.
    84  func (r *productResolver) Reviews(ctx context.Context, obj *model.Product) ([]*model.Review, error) {
    85  	switch obj.Upc {
    86  	case "top-1":
    87  		return []*model.Review{{
    88  			Body: "A highly effective form of birth control.",
    89  		}}, nil
    90  
    91  	case "top-2":
    92  		return []*model.Review{{
    93  			Body: "Fedoras are one of the most fashionable hats around and can look great with a variety of outfits.",
    94  		}}, nil
    95  
    96  	case "top-3":
    97  		return []*model.Review{{
    98  			Body: "This is the last straw. Hat you will wear. 11/10",
    99  		}}, nil
   100  
   101  	}
   102  	return nil, nil
   103  }
   104  
   105  func (r *userResolver) Reviews(ctx context.Context, obj *model.User) ([]*model.Review, error) {
   106  	if obj.ID == "1234" {
   107  		return []*model.Review{{
   108  			Body: "Has an odd fascination with hats.",
   109  		}}, nil
   110  	}
   111  	return nil, nil
   112  }
   113  ```
   114  
   115  > Note
   116  >
   117  > Repeat this step for each of the services in the apollo doc (accounts, products, reviews)
   118  
   119  ## Create the federation gateway
   120  
   121  ```bash
   122  npm install --save @apollo/gateway apollo-server graphql
   123  ```
   124  
   125  ```typescript
   126  const { ApolloServer } = require('apollo-server');
   127  const { ApolloGateway } = require("@apollo/gateway");
   128  
   129  const gateway = new ApolloGateway({
   130      serviceList: [
   131          { name: 'accounts', url: 'http://localhost:4001/query' },
   132          { name: 'products', url: 'http://localhost:4002/query' },
   133          { name: 'reviews', url: 'http://localhost:4003/query' }
   134      ],
   135  });
   136  
   137  const server = new ApolloServer({
   138      gateway,
   139  
   140      subscriptions: false,
   141  });
   142  
   143  server.listen().then(({ url }) => {
   144      console.log(`🚀 Server ready at ${url}`);
   145  });
   146  ```
   147  
   148  ## Start all the services
   149  
   150  In separate terminals:
   151  ```bash
   152  go run accounts/server.go
   153  go run products/server.go
   154  go run reviews/server.go
   155  node gateway/index.js
   156  ```
   157  
   158  ## Query the federated gateway
   159  
   160  The examples from the apollo doc should all work, eg
   161  
   162  ```graphql
   163  query {
   164    me {
   165      username
   166      reviews {
   167        body
   168        product {
   169          name
   170          upc
   171        }
   172      }
   173    }
   174  }
   175  ```
   176  
   177  should return
   178  
   179  ```json
   180  {
   181    "data": {
   182      "me": {
   183        "username": "Me",
   184        "reviews": [
   185          {
   186            "body": "A highly effective form of birth control.",
   187            "product": {
   188              "name": "Trilby",
   189              "upc": "top-1"
   190            }
   191          },
   192          {
   193            "body": "Fedoras are one of the most fashionable hats around and can look great with a variety of outfits.",
   194            "product": {
   195              "name": "Trilby",
   196              "upc": "top-1"
   197            }
   198          }
   199        ]
   200      }
   201    }
   202  }
   203  ```
   204  
   205  ## Explicit `@requires` Directive
   206  If you need to support **nested** or **array** fields in the `@requires` directive, this can be enabled in the configuration by setting `federation.options.explicit_requires` to true.
   207  
   208  ```yml
   209  federation:
   210    filename: graph/federation.go
   211    package: graph
   212    version: 2
   213    options:
   214      explicit_requires: true
   215  ```
   216  
   217  Enabling this will generate corresponding functions with the entity representations received in the request. This allows for the entity model to be explicitly populated with the required data provided.
   218  
   219  ### Example
   220  Take a simple todo app schema that needs to provide a formatted status text to be used across all clients by referencing the assignee's name.
   221  
   222  ```graphql
   223  type Todo @key(fields:"id") {
   224    id: ID!
   225    text: String!
   226    statusText: String! @requires(fields: "assignee { name }")
   227    status: String!
   228    owner: User!
   229    assignee: User!
   230  }
   231  
   232  type User @key(fields:"id") {
   233    id: ID!
   234    name: String! @external
   235  }
   236  ```
   237  
   238  A `PopulateTodoRequires` function is generated, and can be modified accordingly to use the todo representation with the assignee name.
   239  
   240  ```golang
   241  // PopulateTodoRequires is the requires populator for the Todo entity.
   242  func (ec *executionContext) PopulateTodoRequires(ctx context.Context, entity *model.Todo, reps map[string]interface{}) error {
   243  	if reps["assignee"] != nil {
   244  		entity.StatusText = entity.Status + " by " + reps["assignee"].(map[string]interface{})["name"].(string)
   245  	}
   246  	return nil
   247  }
   248  ```