From 01d3e169af3dd0f5a444fce952268dd4b762ed6b Mon Sep 17 00:00:00 2001
From: Dariusz Kuc <9501705+dariuszkuc@users.noreply.github.com>
Date: Tue, 1 Nov 2022 09:14:31 -0500
Subject: [PATCH] graphql-go example integration (#255)
Example `graphql-go` integration that is using https://github.com/dariuszkuc/graphql/releases/tag/v0.9.0-federation
NOTE: this is a temporary solution while we are waiting for `graphql-go` PRs to get merged ([651](https://github.com/graphql-go/graphql/pull/651), [652](https://github.com/graphql-go/graphql/pull/652) and [653](https://github.com/graphql-go/graphql/pull/653)).
Resolves: https://github.com/apollographql/apollo-federation-subgraph-compatibility/issues/74
---
.../workflows/test-subgraph-graphql-go.yaml | 14 +
README.md | 1 +
implementations/graphql-go/Dockerfile | 9 +
.../graphql-go/docker-compose.yaml | 6 +
implementations/graphql-go/go.mod | 10 +
implementations/graphql-go/go.sum | 4 +
implementations/graphql-go/metadata.yaml | 3 +
implementations/graphql-go/model/models.go | 47 +++
implementations/graphql-go/products.graphql | 56 ++++
implementations/graphql-go/resolver/data.go | 78 +++++
.../graphql-go/resolver/resolvers.go | 146 +++++++++
implementations/graphql-go/server.go | 300 ++++++++++++++++++
12 files changed, 674 insertions(+)
create mode 100644 .github/workflows/test-subgraph-graphql-go.yaml
create mode 100644 implementations/graphql-go/Dockerfile
create mode 100644 implementations/graphql-go/docker-compose.yaml
create mode 100644 implementations/graphql-go/go.mod
create mode 100644 implementations/graphql-go/go.sum
create mode 100644 implementations/graphql-go/metadata.yaml
create mode 100644 implementations/graphql-go/model/models.go
create mode 100644 implementations/graphql-go/products.graphql
create mode 100644 implementations/graphql-go/resolver/data.go
create mode 100644 implementations/graphql-go/resolver/resolvers.go
create mode 100644 implementations/graphql-go/server.go
diff --git a/.github/workflows/test-subgraph-graphql-go.yaml b/.github/workflows/test-subgraph-graphql-go.yaml
new file mode 100644
index 000000000..875cb83ef
--- /dev/null
+++ b/.github/workflows/test-subgraph-graphql-go.yaml
@@ -0,0 +1,14 @@
+name: GraphQL Go Test
+
+on:
+ pull_request:
+ branches:
+ - main
+ paths:
+ - 'implementations/graphql-go/**'
+
+jobs:
+ compatibility:
+ uses: ./.github/workflows/test-subgraph.yaml
+ with:
+ library: "graphql-go"
\ No newline at end of file
diff --git a/README.md b/README.md
index bd581d5bd..51e0424a1 100644
--- a/README.md
+++ b/README.md
@@ -57,6 +57,7 @@ The following open-source GraphQL server libraries and hosted subgraphs provide
Library | Federation 1 Support | Federation 2 Support |
+GraphQL Go | _service | 🟢 |
---|
@key (single) | 🟢 |
---|
@key (multi) | 🟢 |
---|
@key (composite) | 🟢 |
---|
repeatable @key | 🟢 |
---|
@requires | 🟢 |
---|
@provides | 🟢 |
---|
federated tracing | 🔲 |
---|
| @link | 🟢 |
---|
@shareable | 🟢 |
---|
@tag | 🟢 |
---|
@override | 🟢 |
---|
@inaccessible | 🟢 |
---|
|
gqlgen | _service | 🟢 |
---|
@key (single) | 🟢 |
---|
@key (multi) | 🟢 |
---|
@key (composite) | 🟢 |
---|
repeatable @key | 🟢 |
---|
@requires | 🔲 |
---|
@provides | 🟢 |
---|
federated tracing | 🟢 |
---|
| @link | 🟢 |
---|
@shareable | 🟢 |
---|
@tag | 🟢 |
---|
@override | 🟢 |
---|
@inaccessible | 🟢 |
---|
|
diff --git a/implementations/graphql-go/Dockerfile b/implementations/graphql-go/Dockerfile
new file mode 100644
index 000000000..19445b263
--- /dev/null
+++ b/implementations/graphql-go/Dockerfile
@@ -0,0 +1,9 @@
+FROM golang:1.19
+
+WORKDIR /go/src/server
+COPY . .
+
+RUN go get -d -v ./...
+RUN go install -v ./...
+
+CMD go run server.go
diff --git a/implementations/graphql-go/docker-compose.yaml b/implementations/graphql-go/docker-compose.yaml
new file mode 100644
index 000000000..0d5a0d5ea
--- /dev/null
+++ b/implementations/graphql-go/docker-compose.yaml
@@ -0,0 +1,6 @@
+services:
+ products:
+ # must be relative to the root of the project
+ build: implementations/graphql-go
+ ports:
+ - 4001:4001
diff --git a/implementations/graphql-go/go.mod b/implementations/graphql-go/go.mod
new file mode 100644
index 000000000..fcab4f109
--- /dev/null
+++ b/implementations/graphql-go/go.mod
@@ -0,0 +1,10 @@
+module graphql-go-compatibility
+
+go 1.19
+
+require (
+ github.com/graphql-go/graphql v0.8.0
+ github.com/graphql-go/handler v0.2.3
+)
+
+replace github.com/graphql-go/graphql => github.com/dariuszkuc/graphql v0.9.0-federation
diff --git a/implementations/graphql-go/go.sum b/implementations/graphql-go/go.sum
new file mode 100644
index 000000000..c9d8b5204
--- /dev/null
+++ b/implementations/graphql-go/go.sum
@@ -0,0 +1,4 @@
+github.com/dariuszkuc/graphql v0.9.0-federation h1:dHxP3Z+wX/hRkj/3jaxGXb7iG+Zw3fjW1frQbgsq3lI=
+github.com/dariuszkuc/graphql v0.9.0-federation/go.mod h1:nKiHzRM0qopJEwCITUuIsxk9PlVlwIiiI8pnJEhordQ=
+github.com/graphql-go/handler v0.2.3 h1:CANh8WPnl5M9uA25c2GBhPqJhE53Fg0Iue/fRNla71E=
+github.com/graphql-go/handler v0.2.3/go.mod h1:leLF6RpV5uZMN1CdImAxuiayrYYhOk33bZciaUGaXeU=
diff --git a/implementations/graphql-go/metadata.yaml b/implementations/graphql-go/metadata.yaml
new file mode 100644
index 000000000..148a27791
--- /dev/null
+++ b/implementations/graphql-go/metadata.yaml
@@ -0,0 +1,3 @@
+fullName: GraphQL Go
+language: Go
+documentation: https://github.com/graphql-go/graphql
diff --git a/implementations/graphql-go/model/models.go b/implementations/graphql-go/model/models.go
new file mode 100644
index 000000000..92b031581
--- /dev/null
+++ b/implementations/graphql-go/model/models.go
@@ -0,0 +1,47 @@
+package model
+
+type User struct {
+ // AverageProductsCreatedPerYear *int `json:"averageProductsCreatedPerYear"`
+ Email string `json:"email"`
+ Name *string `json:"name"`
+ TotalProductsCreated *int `json:"totalProductsCreated"`
+ YearsOfEmployment int `json:"yearsOfEmployment"`
+}
+
+type CaseStudy struct {
+ CaseNumber string `json:"caseNumber"`
+ Description *string `json:"description"`
+}
+
+type DeprecatedProduct struct {
+ Sku string `json:"sku"`
+ Package string `json:"package"`
+ Reason *string `json:"reason"`
+ CreatedBy *User `json:"createdBy"`
+}
+
+type Product struct {
+ ID string `json:"id"`
+ Sku *string `json:"sku"`
+ Package *string `json:"package"`
+ Variation *ProductVariation `json:"variation"`
+ Dimensions *ProductDimension `json:"dimensions"`
+ CreatedBy *User `json:"createdBy"`
+ Notes *string `json:"notes"`
+ Research []*ProductResearch `json:"research"`
+}
+
+type ProductDimension struct {
+ Size *string `json:"size"`
+ Weight *float64 `json:"weight"`
+ Unit *string `json:"unit"`
+}
+
+type ProductResearch struct {
+ Study *CaseStudy `json:"study"`
+ Outcome *string `json:"outcome"`
+}
+
+type ProductVariation struct {
+ ID string `json:"id"`
+}
diff --git a/implementations/graphql-go/products.graphql b/implementations/graphql-go/products.graphql
new file mode 100644
index 000000000..9f727ae17
--- /dev/null
+++ b/implementations/graphql-go/products.graphql
@@ -0,0 +1,56 @@
+extend schema
+ @link(
+ url: "https://specs.apollo.dev/federation/v2.0",
+ import: ["@extends", "@external", "@inaccessible", "@key", "@override", "@provides", "@requires", "@shareable", "@tag"]
+ )
+
+type Product @key(fields: "id") @key(fields: "sku package") @key(fields: "sku variation { id }") {
+ id: ID!
+ sku: String
+ package: String
+ variation: ProductVariation
+ dimensions: ProductDimension
+ createdBy: User @provides(fields: "totalProductsCreated")
+ notes: String @tag(name: "internal")
+ research: [ProductResearch!]!
+}
+
+type DeprecatedProduct @key(fields: "sku package") {
+ sku: String!
+ package: String!
+ reason: String
+ createdBy: User
+}
+
+type ProductVariation {
+ id: ID!
+}
+
+type ProductResearch @key(fields: "study { caseNumber }") {
+ study: CaseStudy!
+ outcome: String
+}
+
+type CaseStudy {
+ caseNumber: ID!
+ description: String
+}
+
+type ProductDimension @shareable {
+ size: String
+ weight: Float
+ unit: String @inaccessible
+}
+
+extend type Query {
+ product(id: ID!): Product
+ deprecatedProduct(sku: String!, package: String!): DeprecatedProduct @deprecated(reason: "Use product query instead")
+}
+
+extend type User @key(fields: "email") {
+ averageProductsCreatedPerYear: Int @requires(fields: "totalProductsCreated yearsOfEmployment")
+ email: ID! @external
+ name: String @override(from: "users")
+ totalProductsCreated: Int @external
+ yearsOfEmployment: Int! @external
+}
diff --git a/implementations/graphql-go/resolver/data.go b/implementations/graphql-go/resolver/data.go
new file mode 100644
index 000000000..14279e8e3
--- /dev/null
+++ b/implementations/graphql-go/resolver/data.go
@@ -0,0 +1,78 @@
+package resolver
+
+import "graphql-go-compatibility/model"
+
+var federationSku = "federation"
+var federationPackage = "@apollo/federation"
+
+var studioSku = "studio"
+
+var productSize = "small"
+var productWeight = 1.0
+var productUnit = "kg"
+
+var products = []*model.Product{
+ {
+ ID: "apollo-federation",
+ Sku: &federationSku,
+ Package: &federationPackage,
+ Variation: &model.ProductVariation{ID: "OSS"},
+ Dimensions: &model.ProductDimension{
+ Size: &productSize,
+ Weight: &productWeight,
+ Unit: &productUnit,
+ },
+ },
+ {
+ ID: "apollo-studio",
+ Sku: &studioSku,
+ Package: nil,
+ Variation: &model.ProductVariation{ID: "platform"},
+ Dimensions: &model.ProductDimension{
+ Size: &productSize,
+ Weight: &productWeight,
+ Unit: &productUnit,
+ },
+ },
+}
+
+var userName = "Jane Smith"
+var totalProductsCreated = 1337
+
+var users = []*model.User{
+ {
+ Email: "support@apollographql.com",
+ Name: &userName,
+ TotalProductsCreated: &totalProductsCreated,
+ },
+}
+
+var DefaultUser = users[0]
+
+var deprecationReason = "Migrate to Federation V2"
+
+var deprecatedProducts = []*model.DeprecatedProduct{
+ {
+ Sku: "apollo-federation-v1",
+ Package: "@apollo/federation-v1",
+ Reason: &deprecationReason,
+ },
+}
+
+var federationStudyDescription = "Federation Study"
+var studioStudyDescription = "Studio Study"
+
+var research = []*model.ProductResearch{
+ {
+ Study: &model.CaseStudy{
+ CaseNumber: "1234",
+ Description: &federationStudyDescription,
+ },
+ },
+ {
+ Study: &model.CaseStudy{
+ CaseNumber: "1235",
+ Description: &studioStudyDescription,
+ },
+ },
+}
diff --git a/implementations/graphql-go/resolver/resolvers.go b/implementations/graphql-go/resolver/resolvers.go
new file mode 100644
index 000000000..487892abe
--- /dev/null
+++ b/implementations/graphql-go/resolver/resolvers.go
@@ -0,0 +1,146 @@
+package resolver
+
+import (
+ "math"
+
+ "graphql-go-compatibility/model"
+)
+
+func FindDeprecatedProductBySkuAndPackage(sku string, packageArg string) (*model.DeprecatedProduct, error) {
+ for i := range deprecatedProducts {
+ if deprecatedProducts[i].Sku == sku && deprecatedProducts[i].Package == packageArg {
+ return deprecatedProducts[i], nil
+ }
+ }
+ return nil, nil
+}
+
+func FindProductById(id string) (*model.Product, error) {
+ for i := range products {
+ if products[i].ID == id {
+ return products[i], nil
+ }
+ }
+ return nil, nil
+}
+
+func FindProductBySkuAndPackage(sku string, packageArg string) (*model.Product, error) {
+ for i := range products {
+ if *products[i].Sku == sku && *products[i].Package == packageArg {
+ return products[i], nil
+ }
+ }
+ return nil, nil
+}
+
+func FindProductBySkuAndVariationID(sku string, variationID string) (*model.Product, error) {
+ for i := range products {
+ if *products[i].Sku == sku && products[i].Variation.ID == variationID {
+ return products[i], nil
+ }
+ }
+ return nil, nil
+}
+
+func FindProductResearchByProductId(product *model.Product) ([]*model.ProductResearch, error) {
+ switch product.ID {
+ case "apollo-federation":
+ return research[:1], nil
+ case "apollo-studio":
+ return research[1:], nil
+ default:
+ return nil, nil
+ }
+}
+
+func CalculateAverageProductsCreatedPerYear(user *model.User) (*int, error) {
+ if user.TotalProductsCreated == nil {
+ return nil, nil
+ }
+ var avgProductsCreated = int(math.Round(float64(*user.TotalProductsCreated) / float64(user.YearsOfEmployment)))
+ return &avgProductsCreated, nil
+}
+
+// entity resolvers
+
+// @key(fields: "sku package")
+func DeprecatedProductEntityResolver(params map[string]interface{}) (*model.DeprecatedProduct, error) {
+ sku, skuOk := params["sku"].(string)
+ pkg, pkgOk := params["package"].(string)
+ if skuOk && pkgOk {
+ return FindDeprecatedProductBySkuAndPackage(sku, pkg)
+ }
+ return nil, nil
+}
+
+// @key(fields: "id")
+// @key(fields: "sku package")
+// @key(fields: "sku variation { id }")
+func ProductEntityResolver(params map[string]interface{}) (*model.Product, error) {
+ id, ok := params["id"].(string)
+ if ok {
+ return FindProductById(id)
+ } else {
+ sku, skuOk := params["sku"].(string)
+ if skuOk {
+ pkg, pkgOk := params["package"].(string)
+ if pkgOk {
+ return FindProductBySkuAndPackage(sku, pkg)
+ } else {
+ variation, varOk := params["variation"].(map[string]interface{})
+ if varOk {
+ varId, varIdOk := variation["id"].(string)
+ if varIdOk {
+ return FindProductBySkuAndVariationID(sku, varId)
+ }
+ }
+ }
+ }
+ }
+ return nil, nil
+}
+
+// @key(fields: "study { caseNumber }")
+func ProductResearchEntityResolver(params map[string]interface{}) (*model.ProductResearch, error) {
+ study, ok := params["study"].(map[string]interface{})
+ if ok {
+ caseNumber, caseOk := study["caseNumber"].(string)
+ if caseOk {
+ for i := range research {
+ if research[i].Study.CaseNumber == caseNumber {
+ return research[i], nil
+ }
+ }
+ }
+ }
+ return nil, nil
+}
+
+// @key(fields: "email")
+func UserEntityResolver(params map[string]interface{}) (*model.User, error) {
+ email, ok := params["email"].(string)
+ if ok {
+ for i := range users {
+ var user *model.User
+ if users[i].Email == email {
+ user = users[i]
+ }
+
+ totalProducts, totalSpecified := params["totalProductsCreated"].(float64)
+ if totalSpecified {
+ total := int(totalProducts)
+ user.TotalProductsCreated = &total
+ }
+
+ yearsOfEmployment, yearsSpecified := params["yearsOfEmployment"].(float64)
+ if yearsSpecified {
+ user.YearsOfEmployment = int(yearsOfEmployment)
+ }
+
+ if user != nil {
+ return user, nil
+ }
+ }
+ }
+ return nil, nil
+}
diff --git a/implementations/graphql-go/server.go b/implementations/graphql-go/server.go
new file mode 100644
index 000000000..4bcb4a547
--- /dev/null
+++ b/implementations/graphql-go/server.go
@@ -0,0 +1,300 @@
+package main
+
+import (
+ "log"
+ "net/http"
+
+ "graphql-go-compatibility/model"
+ "graphql-go-compatibility/resolver"
+
+ "github.com/graphql-go/graphql"
+ "github.com/graphql-go/graphql/federation"
+ "github.com/graphql-go/handler"
+)
+
+var userType = graphql.NewObject(graphql.ObjectConfig{
+ Name: "User",
+ Fields: graphql.Fields{
+ "averageProductsCreatedPerYear": &graphql.Field{
+ Type: graphql.Int,
+ Resolve: func(p graphql.ResolveParams) (interface{}, error) {
+ user, ok := p.Source.(*model.User)
+ if ok {
+ return resolver.CalculateAverageProductsCreatedPerYear(user)
+ }
+ return nil, nil
+ },
+ AppliedDirectives: []*graphql.AppliedDirective{
+ federation.RequiresAppliedDirective("totalProductsCreated yearsOfEmployment"),
+ },
+ },
+ "email": &graphql.Field{
+ Type: graphql.NewNonNull(graphql.ID),
+ AppliedDirectives: []*graphql.AppliedDirective{
+ federation.ExternalAppliedDirective,
+ },
+ },
+ "name": &graphql.Field{
+ Type: graphql.String,
+ AppliedDirectives: []*graphql.AppliedDirective{
+ federation.OverrideAppliedDirective("users"),
+ },
+ },
+ "totalProductsCreated": &graphql.Field{
+ Type: graphql.Int,
+ AppliedDirectives: []*graphql.AppliedDirective{
+ federation.ExternalAppliedDirective,
+ },
+ },
+ "yearsOfEmployment": &graphql.Field{
+ Type: graphql.NewNonNull(graphql.Int),
+ AppliedDirectives: []*graphql.AppliedDirective{
+ federation.ExternalAppliedDirective,
+ },
+ },
+ },
+ AppliedDirectives: []*graphql.AppliedDirective{
+ federation.KeyAppliedDirective("email", true),
+ },
+})
+
+var productVariationType = graphql.NewObject(graphql.ObjectConfig{
+ Name: "ProductVariation",
+ Fields: graphql.Fields{
+ "id": &graphql.Field{
+ Type: graphql.NewNonNull(graphql.ID),
+ },
+ },
+})
+
+var productDimensionType = graphql.NewObject(graphql.ObjectConfig{
+ Name: "ProductDimension",
+ Fields: graphql.Fields{
+ "size": &graphql.Field{
+ Type: graphql.String,
+ },
+ "weight": &graphql.Field{
+ Type: graphql.Float,
+ },
+ "unit": &graphql.Field{
+ Type: graphql.String,
+ AppliedDirectives: []*graphql.AppliedDirective{
+ federation.InaccessibleAppliedDirective,
+ },
+ },
+ },
+ AppliedDirectives: []*graphql.AppliedDirective{
+ federation.ShareableAppliedDirective,
+ },
+})
+
+var caseStudyType = graphql.NewObject(graphql.ObjectConfig{
+ Name: "CaseStudy",
+ Fields: graphql.Fields{
+ "caseNumber": &graphql.Field{
+ Type: graphql.NewNonNull(graphql.ID),
+ },
+ "description": &graphql.Field{
+ Type: graphql.String,
+ },
+ },
+})
+
+var productResearchType = graphql.NewObject(graphql.ObjectConfig{
+ Name: "ProductResearch",
+ Fields: graphql.Fields{
+ "study": &graphql.Field{
+ Type: graphql.NewNonNull(caseStudyType),
+ },
+ "outcome": &graphql.Field{
+ Type: graphql.String,
+ },
+ },
+ AppliedDirectives: []*graphql.AppliedDirective{
+ federation.KeyAppliedDirective("study { caseNumber }", true),
+ },
+})
+
+var productType = graphql.NewObject(graphql.ObjectConfig{
+ Name: "Product",
+ Fields: graphql.Fields{
+ "id": &graphql.Field{
+ Type: graphql.NewNonNull(graphql.ID),
+ },
+ "sku": &graphql.Field{
+ Type: graphql.String,
+ },
+ "package": &graphql.Field{
+ Type: graphql.String,
+ },
+ "variation": &graphql.Field{
+ Type: productVariationType,
+ },
+ "dimensions": &graphql.Field{
+ Type: productDimensionType,
+ },
+ "createdBy": &graphql.Field{
+ Type: userType,
+ Resolve: func(p graphql.ResolveParams) (interface{}, error) {
+ return resolver.DefaultUser, nil
+ },
+ AppliedDirectives: []*graphql.AppliedDirective{
+ federation.ProvidesAppliedDirective("totalProductsCreated"),
+ },
+ },
+ "notes": &graphql.Field{
+ Type: graphql.String,
+ AppliedDirectives: []*graphql.AppliedDirective{
+ federation.TagAppliedDirective("internal"),
+ },
+ },
+ "research": &graphql.Field{
+ Type: graphql.NewNonNull(graphql.NewList(graphql.NewNonNull(productResearchType))),
+ Resolve: func(p graphql.ResolveParams) (interface{}, error) {
+ product, ok := p.Source.(*model.Product)
+ if ok {
+ return resolver.FindProductResearchByProductId(product)
+ }
+ return nil, nil
+ },
+ },
+ },
+ AppliedDirectives: []*graphql.AppliedDirective{
+ federation.KeyAppliedDirective("id", true),
+ federation.KeyAppliedDirective("sku package", true),
+ federation.KeyAppliedDirective("sku variation { id }", true),
+ },
+})
+
+var deprecatedProductType = graphql.NewObject(graphql.ObjectConfig{
+ Name: "DeprecatedProduct",
+ Fields: graphql.Fields{
+ "sku": &graphql.Field{
+ Type: graphql.NewNonNull(graphql.String),
+ },
+ "package": &graphql.Field{
+ Type: graphql.NewNonNull(graphql.String),
+ },
+ "reason": &graphql.Field{
+ Type: graphql.String,
+ },
+ "createdBy": &graphql.Field{
+ Type: userType,
+ Resolve: func(p graphql.ResolveParams) (interface{}, error) {
+ return resolver.DefaultUser, nil
+ },
+ },
+ },
+ AppliedDirectives: []*graphql.AppliedDirective{
+ federation.KeyAppliedDirective("sku package", true),
+ },
+})
+
+var rootQuery = graphql.NewObject(graphql.ObjectConfig{
+ Name: "Query",
+ Fields: graphql.Fields{
+ "product": &graphql.Field{
+ Type: productType,
+ Args: graphql.FieldConfigArgument{
+ "id": &graphql.ArgumentConfig{
+ Type: graphql.NewNonNull(graphql.ID),
+ },
+ },
+ Resolve: func(params graphql.ResolveParams) (interface{}, error) {
+ id, ok := params.Args["id"].(string)
+ if ok {
+ return resolver.FindProductById(id)
+ }
+ return nil, nil
+ },
+ },
+ "deprecatedProduct": &graphql.Field{
+ Type: deprecatedProductType,
+ Args: graphql.FieldConfigArgument{
+ "sku": &graphql.ArgumentConfig{
+ Type: graphql.NewNonNull(graphql.String),
+ },
+ "package": &graphql.ArgumentConfig{
+ Type: graphql.NewNonNull(graphql.String),
+ },
+ },
+ Resolve: func(params graphql.ResolveParams) (interface{}, error) {
+ sku, skuOk := params.Args["sku"].(string)
+ pkg, pkgOk := params.Args["package"].(string)
+ if skuOk && pkgOk {
+ return resolver.FindDeprecatedProductBySkuAndPackage(sku, pkg)
+ }
+ return nil, nil
+ },
+ DeprecationReason: "Use product query instead",
+ },
+ },
+})
+
+var schema, _ = federation.NewFederatedSchema(federation.FederatedSchemaConfig{
+ EntitiesFieldResolver: func(p graphql.ResolveParams) (interface{}, error) {
+ representations, ok := p.Args["representations"].([]interface{})
+ results := make([]interface{}, 0)
+ if ok {
+ for _, representation := range representations {
+ raw, isAny := representation.(map[string]interface{})
+ if isAny {
+ typeName, typeSpecified := raw["__typename"].(string)
+ if typeSpecified {
+ switch typeName {
+ case "Product":
+ product, _ := resolver.ProductEntityResolver(raw)
+ results = append(results, product)
+ case "User":
+ user, _ := resolver.UserEntityResolver(raw)
+ results = append(results, user)
+ case "DeprecatedProduct":
+ deprecatedProduct, _ := resolver.DeprecatedProductEntityResolver(raw)
+ results = append(results, deprecatedProduct)
+ case "ProductResearch":
+ research, _ := resolver.ProductResearchEntityResolver(raw)
+ results = append(results, research)
+ }
+ } else {
+ panic("Invalid entity representation - missing __typename")
+ }
+ }
+ }
+ }
+ return results, nil
+ },
+ EntityTypeResolver: func(p graphql.ResolveTypeParams) *graphql.Object {
+ if _, ok := p.Value.(*model.Product); ok {
+ return productType
+ }
+ if _, ok := p.Value.(*model.User); ok {
+ return userType
+ }
+ if _, ok := p.Value.(*model.DeprecatedProduct); ok {
+ return deprecatedProductType
+ }
+ if _, ok := p.Value.(*model.ProductResearch); ok {
+ return productResearchType
+ }
+ return nil
+ },
+ SchemaConfig: graphql.SchemaConfig{
+ Query: rootQuery,
+ Types: []graphql.Type{
+ userType,
+ },
+ },
+})
+
+func main() {
+ handler := handler.New(&handler.Config{
+ Schema: &schema,
+ Pretty: true,
+ GraphiQL: true,
+ })
+
+ http.Handle("/", handler)
+
+ log.Printf("graphql-go server is accepting requests at http://localhost:4001/")
+ log.Fatal(http.ListenAndServe(":4001", nil))
+}