Используя 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)),
]
,... теперь приводит к ошибке Федерации Аполлона:
Когда я проверил этот вопрос, я обнаружил, что 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 не может успешно получить эту схему микросервиса?
Насколько я понимаю, и после успешного внедрения Ариадны, для работы федеративных сервисов в схеме должно быть поле _service, типа _Service, у которого есть поле sdl; whcih возвращает всю схему в виде строки. Это очень странно, поскольку это просто повторение, по существу имеющее поле в схеме, которое возвращает указанную схему. Вы правы в том, что графен не поддерживает это изначально, но и почти каждый бэкенд, пытающийся использовать graphql, не поддерживает, например, Ариадна, мы просто определяем, что их документация говорит, что должно быть.





Решение на самом деле представляет собой небольшой взлом схемы, которая автоматически генерируется через 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
}
Строка, которую я не заметил на веб-сайте 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
Федерация 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)? Когда доберусь до работы, тоже попробую..
Итак, я только что опробовал ваше решение, но Федерация все еще не работает, потому что она считает, что sdl имеет тип ServiceField, где он должен быть _Service. Я также утверждаю в своем ответе в update 1, что sdl, полученный из sdl, не содержит элементов федерации, теперь я добавлю update2, чтобы документировать это более четко.
Вы должны быть в состоянии определить имя SerficeField, для меня это не было проблемой. На самом деле вам не нужно заменять запрос расширения, он работал для меня обоих. Единственными элементами федерации, которые вам нужны, являются конечная точка и ее тип, вам не нужно определять директиву. Я заставил все это работать, если вы посмотрите на полную реализацию, которую я связал в этом комментарии :)
Это хорошо, но у меня это все еще не работает, я собираюсь принять свой собственный ответ, поскольку он решает проблему.
Эта библиотека пипов может помочь 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)
и это работает.
Федеративные службы должны реализовать спецификация федерации. В Apollo это делается с помощью функции
buildFederatedSchema. Я не уверен, что графен поддерживает что-то подобное.