Проблемы интеграции Python graphene с Apollo Federation

Используя python для реализации GraphQL в нескольких микросервисах, некоторые используют Ariadne, а некоторые — графен (и graphene-Django). Из-за микросервисной архитектуры было выбрано, что Apollo Federation объединит схемы из разных микросервисов.

С Ариадной это очень просто (сначала схема) и небольшой пример:

from ariadne import QueryType, gql, make_executable_schema, MutationType, ObjectType
from ariadne.asgi import GraphQL

query = QueryType()
mutation = MutationType()

sdl = """
type _Service {
    sdl: String
}

type Query {
    _service: _Service!
    hello: String
}
"""

@query.field("hello")
async def resolve_hello(_, info):
    return "Hello"


@query.field("_service")
def resolve__service(_, info):
    return {
        "sdl": sdl
    }

schema = make_executable_schema(gql(sdl), query)
app = GraphQL(schema, debug=True)

Теперь это без проблем подхватывается с Apollo Federation:

const { ApolloServer } = require("apollo-server");
const { ApolloGateway } = require("@apollo/gateway");


const gateway = new ApolloGateway({
    serviceList: [
      // { name: 'msone', url: 'http://192.168.2.222:9091' },
      { name: 'mstwo', url: 'http://192.168.2.222:9092/graphql/' },
    ]
  });

  (async () => {
    const { schema, executor } = await gateway.load();
    const server = new ApolloServer({ schema, executor });
    // server.listen();
    server.listen(
      3000, "0.0.0.0"
      ).then(({ url }) => {
      console.info(`? Server ready at ${url}`);
    });
  })();

Для чего я могу запускать запросы graphql к серверу на 3000.

Но с использованием графена, пытаясь реализовать ту же функциональность, что и Ариадна:

import graphene

class _Service(graphene.ObjectType):
    sdl = graphene.String()

class Query(graphene.ObjectType):

    service = graphene.Field(_Service, name = "_service")
    hello = graphene.String()

    def resolve_hello(self, info, **kwargs):
        return "Hello world!"

    def resolve_service(self, info, **kwargs):
        from config.settings.shared import get_loaded_sdl
        res = get_loaded_sdl()  # gets the schema defined later in this file
        return _Service(sdl=res)

schema = graphene.Schema(query=Query)

# urls.py
urlpatterns = [
    url(r'^graphql/$', GraphQLView.as_view(graphiql=True)),
]

,... теперь приводит к ошибке Федерации Аполлона:

GraphQLSchemaValidationError: Тип запроса должен определять одно или несколько полей.

Когда я проверил этот вопрос, я обнаружил, что apollo вызывает микросервис с запросом graphql:

query GetServiceDefinition { _service { sdl } }

Запуск его на микросервисе через Insomnia/Postman/GraphiQL с Ariadne дает:

{
  "data": {
    "_service": {
      "sdl": "\n\ntype _Service {\n    sdl: String\n}\n\ntype Query {\n    _service: _Service!\n    hello: String\n}\n"
    }
  }
}

# Which expanding the `sdl` part:
type _Service {
    sdl: String
}

type Query {
    _service: _Service!
    hello: String
}

и на микросервисе с Graphene:

{
  "data": {
    "_service": {
      "sdl": "schema {\n  query: Query\n}\n\ntype Query {\n  _service: _Service\n  hello: String\n}\n\ntype _Service {\n  sdl: String\n}\n"
    }
  }
}

# Which expanding the `sdl` part:
schema {
    query: Query
}

type Query {
    _service: _Service
    hello: String
}

type _Service {
    sdl: String
}

Итак, они оба одинаковы для определения того, как получить sdl, я проверил ответ микросервиса и обнаружил, что ответ графена также отправляет правильные данные, с ответом Json «данные», равным:

execution_Result:  OrderedDict([('_service', OrderedDict([('sdl', 'schema {\n  query: Query\n}\n\ntype Query {\n  _service: _Service\n  hello: String\n}\n\ntype _Service {\n  sdl: String\n}\n')]))])

Так в чем может быть причина того, что Apollo Federation не может успешно получить эту схему микросервиса?

Федеративные службы должны реализовать спецификация федерации. В Apollo это делается с помощью функции buildFederatedSchema. Я не уверен, что графен поддерживает что-то подобное.

Daniel Rearden 03.07.2019 14:13

Насколько я понимаю, и после успешного внедрения Ариадны, для работы федеративных сервисов в схеме должно быть поле _service, типа _Service, у которого есть поле sdl; whcih возвращает всю схему в виде строки. Это очень странно, поскольку это просто повторение, по существу имеющее поле в схеме, которое возвращает указанную схему. Вы правы в том, что графен не поддерживает это изначально, но и почти каждый бэкенд, пытающийся использовать graphql, не поддерживает, например, Ариадна, мы просто определяем, что их документация говорит, что должно быть.

jupiar 03.07.2019 16:44
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
2
2 712
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Решение на самом деле представляет собой небольшой взлом схемы, которая автоматически генерируется через graphene. Я думал, что уже пробовал это, и это все еще работало, но я только что сделал это снова, но оно сломалось.

Так что если в Ариадне я добавлю

schema {
    query: Query
}

в sdl Федерация Аполлона также повышает Type Query must define one or more fields.. Без него работает нормально. Тогда я тоже перешел к графену и в функции resolve_service сделал:

def resolve_service(self, info, **kwargs):
    from config.settings.shared import get_loaded_sdl
    res = get_loaded_sdl()
    res = res.replace("schema {\n  query: Query\n}\n\n", "")
    return _Service(sdl=res)

И теперь графен тоже работает, так что я думаю, проблема была в том, что я упустил из виду, кажется, что Apollo Federation не может обрабатывать грамматику схемы:

schema {
    query: Query
}

Обновление 1

Строка, которую я не заметил на веб-сайте Apollo, заключается в следующем:

This SDL does not include the additions of the federation spec above. Given an input like this:

Это ясно при объединении сервисов вместе в Federation, так как это вызовет ошибку:

GraphQLSchemaValidationError: Field "_Service.sdl" can only be defined once.

Таким образом, несмотря на то, что в полной схеме для микросервиса с определением _Service.sdl мы хотим, чтобы эта информация исчезла для строки полной схемы, возвращаемой в качестве возвращаемой строки для _Service.sdl

Обновление 2

Федерация Apollo теперь работает нормально, убедившись, что строка, возвращаемая полем sdl, не содержит спецификаций федерации.

Я думаю, что в графене каждая реализация может отличаться, но в целом вы хотите заменить следующее:

res = get_loaded_sdl()
res = res.replace("schema {\n  query: Query\n}\n\n", "")
res = res.replace("type _Service {\n  sdl: String\n}", "")
res = res.replace("\n  _service: _Service!", "")

А в Ariadne просто нужно определить два sdl, один из которых содержит спецификации федерации (для схемы, возвращаемой службой), а другой без спецификаций федерации (тот, который возвращается полем sdl)

Вы на правильном пути к другому ответу, но похоже, что вам нужно будет кое-что вырезать из печатной версии.

вот как я использовал в проблема с гитхабом

я резюмирую свой код здесь:

schema = ""
class ServiceField(graphene.ObjectType):
    sdl = String()

    def resolve_sdl(parent, _):
        string_schema = str(schema)
        string_schema = string_schema.replace("\n", " ")
        string_schema = string_schema.replace("type Query", "extend type Query")
        string_schema = string_schema.replace("schema {   query: Query   mutation: MutationQuery }", "")
        return string_schema


class Service:
    _service = graphene.Field(ServiceField, name = "_service", resolver=lambda x, _: {})

class Query(
    # ...
    Service,
    graphene.ObjectType,
):
    pass

schema = graphene.Schema(query=Query, types=CUSTOM_ATTRIBUTES_TYPES)

Нет ли ошибки с .replace("type Query", "extend type Query")? После этой замены, как схема действительна, когда вы расширяете то, что сейчас не существует (Query)? Когда доберусь до работы, тоже попробую..

jupiar 04.07.2019 02:09

Итак, я только что опробовал ваше решение, но Федерация все еще не работает, потому что она считает, что sdl имеет тип ServiceField, где он должен быть _Service. Я также утверждаю в своем ответе в update 1, что sdl, полученный из sdl, не содержит элементов федерации, теперь я добавлю update2, чтобы документировать это более четко.

jupiar 04.07.2019 04:08

Вы должны быть в состоянии определить имя SerficeField, для меня это не было проблемой. На самом деле вам не нужно заменять запрос расширения, он работал для меня обоих. Единственными элементами федерации, которые вам нужны, являются конечная точка и ее тип, вам не нужно определять директиву. Я заставил все это работать, если вы посмотрите на полную реализацию, которую я связал в этом комментарии :)

A. Laurent 05.07.2019 16:43

Это хорошо, но у меня это все еще не работает, я собираюсь принять свой собственный ответ, поскольку он решает проблему.

jupiar 07.07.2019 05:23

Эта библиотека пипов может помочь https://pypi.org/project/graphene-federation/

Просто используйте build_schema, и он добавит для вас _service{sdl}:

import graphene
from graphene_federation import build_schema


class Query(graphene.ObjectType):
    ...
    pass

schema = build_schema(Query)  # add _service{sdl} field in Query

Если кому-то интересно, это потому, что графен v2 использует запятые вместо амперсандов в интерфейсах.

interface x implements y, z {
   ...
}

и этот синтаксис больше не работает, обходным путем является обезьяний патч get_sdl

import re

from myproject import Query, Mutation
from graphene_federation import service, build_schema


# monkey patch old get_sdl
old_get_sdl = service.get_sdl

def get_sdl(schema, custom_entities):
    string_schema = old_get_sdl(schema, custom_entities)
    string_schema = string_schema.replace('\n', ' ')

    pattern_types_interfaces = r'type [A-Za-z]* implements ([A-Za-z]+\s*,?\s*)+'
    pattern = re.compile(pattern_types_interfaces)

    string_schema = pattern.sub(lambda matchObj: matchObj.group().replace(',', ' &'), string_schema)
    return string_schema

service.get_sdl = get_sdl
schema = build_schema(Query, mutation=Mutation)

и это работает.

Другие вопросы по теме