Repository: saleor/saleor

Issue:
Support GraphQL federation
### What I'm trying to achieve
I want to use saleor API in GraphQL federation.

### Describe a proposed solution
Graphene does not support federation out of a box, here is a disscuscion https://github.com/graphql-python/graphene/issues/953.



Discussion:
I did create issue on graphene repo to discuss possibility of native support without additional packages.
https://github.com/graphql-python/graphene/issues/1081

Resolving patch:
diff --git a/saleor/graphql/account/types.py b/saleor/graphql/account/types.py
--- a/saleor/graphql/account/types.py
+++ b/saleor/graphql/account/types.py
@@ -2,6 +2,7 @@
 import graphene_django_optimizer as gql_optimizer
 from django.contrib.auth import get_user_model
 from graphene import relay
+from graphene_federation import key
 from graphql_jwt.decorators import login_required
 
 from ...account import models
@@ -33,6 +34,7 @@ class AddressInput(graphene.InputObjectType):
     phone = graphene.String(description="Phone number.")
 
 
+@key(fields="id")
 class Address(CountableDjangoObjectType):
     country = graphene.Field(
         CountryDisplay, required=True, description="Shop's default country."
@@ -105,6 +107,10 @@ def resolve_is_default_billing_address(root: models.Address, _info):
             return True
         return False
 
+    @staticmethod
+    def __resolve_reference(root, _info, **_kwargs):
+        return graphene.Node.get_node_from_global_id(_info, root.id)
+
 
 class CustomerEvent(CountableDjangoObjectType):
     date = graphene.types.datetime.DateTime(
@@ -170,6 +176,7 @@ def resolve_auth_token(root: models.ServiceAccount, _info, **_kwargs):
         return root.auth_token[-4:]
 
 
+@key(fields="id")
 class ServiceAccount(MetadataObjectType, CountableDjangoObjectType):
     permissions = graphene.List(
         PermissionDisplay, description="List of the service's permissions."
@@ -216,7 +223,12 @@ def resolve_tokens(root: models.ServiceAccount, _info, **_kwargs):
     def resolve_meta(root, info):
         return resolve_meta(root, info)
 
+    @staticmethod
+    def __resolve_reference(root, _info, **_kwargs):
+        return graphene.Node.get_node_from_global_id(_info, root.id)
+
 
+@key(fields="id")
 class User(MetadataObjectType, CountableDjangoObjectType):
     addresses = gql_optimizer.field(
         graphene.List(Address, description="List of all user's addresses."),
@@ -339,6 +351,10 @@ def resolve_private_meta(root, _info):
     def resolve_meta(root, _info):
         return resolve_meta(root, _info)
 
+    @staticmethod
+    def __resolve_reference(root, _info, **_kwargs):
+        return graphene.Node.get_node_from_global_id(_info, root.id)
+
 
 class ChoiceValue(graphene.ObjectType):
     raw = graphene.String()
diff --git a/saleor/graphql/api.py b/saleor/graphql/api.py
--- a/saleor/graphql/api.py
+++ b/saleor/graphql/api.py
@@ -1,4 +1,5 @@
 import graphene
+from graphene_federation import build_schema
 
 from .account.schema import AccountMutations, AccountQueries
 from .checkout.schema import CheckoutMutations, CheckoutQueries
@@ -37,7 +38,7 @@ class Query(
     node = graphene.Node.Field()
 
 
-class Mutations(
+class Mutation(
     AccountMutations,
     CheckoutMutations,
     CoreMutations,
@@ -56,4 +57,4 @@ class Mutations(
     pass
 
 
-schema = graphene.Schema(Query, Mutations)
+schema = build_schema(Query, mutation=Mutation)
diff --git a/saleor/graphql/product/types/products.py b/saleor/graphql/product/types/products.py
--- a/saleor/graphql/product/types/products.py
+++ b/saleor/graphql/product/types/products.py
@@ -5,6 +5,7 @@
 from django.conf import settings
 from django.db.models import Prefetch
 from graphene import relay
+from graphene_federation import key
 from graphql.error import GraphQLError
 
 from ....product import models
@@ -205,6 +206,7 @@ class Meta:
         description = "Represents availability of a product in the storefront."
 
 
+@key(fields="id")
 class ProductVariant(CountableDjangoObjectType, MetadataObjectType):
     quantity = graphene.Int(
         required=True,
@@ -415,7 +417,12 @@ def resolve_private_meta(root, _info):
     def resolve_meta(root, _info):
         return resolve_meta(root, _info)
 
+    @staticmethod
+    def __resolve_reference(root, _info, **_kwargs):
+        return graphene.Node.get_node_from_global_id(_info, root.id)
+
 
+@key(fields="id")
 class Product(CountableDjangoObjectType, MetadataObjectType):
     url = graphene.String(
         description="The storefront URL for the product.", required=True
@@ -643,7 +650,12 @@ def resolve_meta(root, _info):
     def resolve_slug(root: models.Product, *_args):
         return root.get_slug()
 
+    @staticmethod
+    def __resolve_reference(root, _info, **_kwargs):
+        return graphene.Node.get_node_from_global_id(_info, root.id)
+
 
+@key(fields="id")
 class ProductType(CountableDjangoObjectType, MetadataObjectType):
     products = gql_optimizer.field(
         PrefetchingConnectionField(
@@ -731,7 +743,12 @@ def resolve_private_meta(root, _info):
     def resolve_meta(root, _info):
         return resolve_meta(root, _info)
 
+    @staticmethod
+    def __resolve_reference(root, _info, **_kwargs):
+        return graphene.Node.get_node_from_global_id(_info, root.id)
+
 
+@key(fields="id")
 class Collection(CountableDjangoObjectType, MetadataObjectType):
     products = gql_optimizer.field(
         PrefetchingConnectionField(
@@ -806,7 +823,12 @@ def resolve_private_meta(root, _info):
     def resolve_meta(root, _info):
         return resolve_meta(root, _info)
 
+    @staticmethod
+    def __resolve_reference(root, _info, **_kwargs):
+        return graphene.Node.get_node_from_global_id(_info, root.id)
+
 
+@key(fields="id")
 class Category(CountableDjangoObjectType, MetadataObjectType):
     ancestors = PrefetchingConnectionField(
         lambda: Category, description="List of ancestors of the category."
@@ -903,7 +925,12 @@ def resolve_private_meta(root, _info):
     def resolve_meta(root, _info):
         return resolve_meta(root, _info)
 
+    @staticmethod
+    def __resolve_reference(root, _info, **_kwargs):
+        return graphene.Node.get_node_from_global_id(_info, root.id)
+
 
+@key(fields="id")
 class ProductImage(CountableDjangoObjectType):
     url = graphene.String(
         required=True,
@@ -925,6 +952,10 @@ def resolve_url(root: models.ProductImage, info, *, size=None):
             url = root.image.url
         return info.context.build_absolute_uri(url)
 
+    @staticmethod
+    def __resolve_reference(root, _info, **_kwargs):
+        return graphene.Node.get_node_from_global_id(_info, root.id)
+
 
 class MoveProductInput(graphene.InputObjectType):
     product_id = graphene.ID(


Test patch: