0

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.

Jacu
  • 11
  • 2

1 Answers1

0

It's hard to tell from plain sight. I would probably try to debug the queries and maybe track SQL queries executed.

Django Debug Toolbar might be helpful to see something at first sight. Otherwise you need to go deeper I guess.

Also I would recommend you to use Strawberry Graphql + Strawberry Django + Strawberry Django Plus. I had plenty of issues with Relay approach in Graphene and I think there was announced end of development for Graphene.

Documentations and examples for Strawberry are really great.

I would appreciate if you posted solution if you'll get one later. I'm curious what the issue was.