I faced a problem with Apollo federation, which in the end I couldn't resolve. I dug through the documentation and issues, which I was able to google out here on stackoverflow and github.
Let me introduce you to a context a little bit. I am using django, together with graphene and graphene-federation. For the sake of this example (and to simplify it as much as I can) let's say I have two graphql APIs combined together using Apollo federation. Let's call them car-service and race-service. These APIs expose two schemas, each has it's own URL. One is /internal/graphql/ and another one is /external/graphql/. For that reason I have separate graphene types for internal and external schemas. The purpose may not be clearly visible in my examples, but imagine I am resolving additional fields here and there and want them to be internal only. So the CarTypeInternal type would have more fields than CarTypeExternal. Those from Car model and some more resolved "manually".
The problem I faced is that my internal entities are not resolved by graphene at all.
In urls.py (both APIs):
urlpatterns = [
path(
"internal/graphql/",
csrf_exempt(
GraphQLView.as_view(
graphiql=True,
schema=internal_schema,
)
),
),
path(
"external/graphql/",
csrf_exempt(
GraphQLView.as_view(
graphiql=True,
schema=external_schema,
)
),
),
]
In external_schema.py of car-service:
@key(fields="uuid")
class CarTypeExternal(django_filters.DjangoObjectType):
class Meta:
model = models.Car
interfaces = (graphene.relay.Node,)
def __resolve_reference(self, info, **kwargs):
query_params = {
"owner_uuid": get_user_by_token(info.context)
"uuid": self.uuid
}
try:
return models.Car.objects.filter(**query_params).first()
except models.Car.DoesNotExist:
return None
In internal_schema.py of car-service:
@key(fields="uuid")
class CarTypeInternal(external_schema.CarTypeExternal):
class Meta:
model = models.Car
interfaces = (graphene.relay.Node,)
def __resolve_reference(self, info, **kwargs):
query_params = {
"uuid": self.uuid
}
try:
return models.Car.objects.filter(**query_params).first()
except models.Car.DoesNotExist:
return None
In external_schema.py of race-service:
@extend(fields="uuid")
class CarTypeExternal(graphene.ObjectType):
uuid = external(graphene.UUID(required=True))
class RaceTypeExternal(django_filters.DjangoObjectType):
cars = graphene.List(CarTypeExternal)
class Meta:
model = models.Race
interfaces = (graphene.relay.Node,)
def resolve_cars(self, info, **kwargs):
cars = []
for (
race_car
) in models.RaceCar.objects.filter(
race__uuid=self.uuid
):
if race_car.car_uuid is not None:
cars.append(
CarTypeExternal(
uuid=race_car.car_uuid
)
)
return analysis_runs
class Query(graphene.ObjectType):
race = graphene.Field(
RaceTypeExternal,
uuid=graphene.UUID(required=True),
),
)
@staticmethod
def resolve_race(self, info, uuid):
return models.Race.objects.get(
uuid=uuid,
)
In internal_schema.py of race-service:
@extend(fields="uuid")
class CarTypeInternal(graphene.ObjectType):
uuid = external(graphene.UUID(required=True))
class RaceTypeInternal(RaceTypeExternal):
cars = graphene.List(CarTypeInternal)
class Meta:
model = models.Race
interfaces = (graphene.relay.Node,)
def resolve_cars(self, info, **kwargs):
cars = []
for (
race_car
) in models.RaceCar.objects.filter(
race__uuid=self.uuid
):
if race_car.car_uuid is not None:
cars.append(
CarTypeInternal(
uuid=race_car.car_uuid
)
)
return analysis_runs
class Query(graphene.ObjectType):
race = graphene.Field(
RaceTypeInternal,
uuid=graphene.UUID(required=True),
),
)
@staticmethod
def resolve_race(self, info, uuid):
driver_uuid = get_user_by_token(info.context)
return models.Race.objects.get(
uuid=uuid,
driver_uuid=driver_uuid,
)
As you can see CarTypeExternal and CarTypeInternal are graphene types entities. Internal entity inherits from external. RaceTypeExternal and RaceTypeInternal which are resolving those fields are using inheritance as well in the same manner.
This were my results with race query.
External API
{
"race": {
"uuid": "7ac2b50f-5db8-4425-9b59-0a714a028ea0",
"name": "Nurburgring",
"cars": [{
"uuid": "3ba0c10f-0553-4565-a208-2c45d23f9145",
"name": "Audi"
},
{
"uuid": "c6532341-b521-4c59-8536-af06d47e7b2a",
"name": "BMW"
}
]
}
}
Internal API (same race uuid, no cars on the list, no list itself)
{
"race": {
"uuid": "7ac2b50f-5db8-4425-9b59-0a714a028ea0",
"name": "Nurburgring",
"cars": null
}
}
Internal API if there are no cars assigned to the race. Please note empty array [] instead of null.
{
"race": {
"uuid": "b92098f7-2d2d-4f6d-a538-1f74f83fce27",
"name": "Car track",
"cars": []
}
}
I expect from internal API query to return full race object along with cars list if available.