将 Python 石墨烯与 Apollo Federation 集成的问题
Problems integrating Python graphene with Apollo Federation
使用python跨多个微服务实现GraphQL,有的使用Ariadne,有的使用graphene(和graphene-Django)。由于微服务架构,选择 Apollo Federation 合并来自不同微服务的模式。
使用 Ariadne 非常简单(首先是模式),还有一个小例子:
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.log(` Server ready at ${url}`);
});
})();
为此,我可以 运行 graphql 查询 3000
上的服务器。
但是,通过使用石墨烯,尝试实现与 Ariadne 相同的功能:
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 响应 "data" 等于:
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没能成功获取到这个微服务schema的原因是什么?
该解决方案实际上是对通过 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:
这在联合中将服务组合在一起时很明显,因为它会引发错误:
GraphQLSchemaValidationError: Field "_Service.sdl" can only be defined once.
因此,尽管在定义 _Service.sdl
的微服务的完整模式中,我们希望 full-schema 的字符串的信息消失 returned 作为 return _Service.sdl
的字符串
更新 2
Apollo Federation 现在运行良好,确保由 sdl
字段 return 编辑的字符串不包含联邦规范。
在石墨烯中,我认为每个实现可能会有所不同,但通常您要替换以下内容:
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,一个包含联合规范(针对服务 returned 的模式),另一个不包含联合规范(returned通过 sdl
字段)
您在另一个答案上的方向不错,但看起来您需要从印刷版中删除一些内容。
这是我在a github issue
中使用的方法
我在这里总结我的代码:
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)
这个 pip 库可以提供帮助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 {
...
}
并且此语法不再有效,解决方法是使用 monkey-patch 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)
而且有效。
使用python跨多个微服务实现GraphQL,有的使用Ariadne,有的使用graphene(和graphene-Django)。由于微服务架构,选择 Apollo Federation 合并来自不同微服务的模式。
使用 Ariadne 非常简单(首先是模式),还有一个小例子:
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.log(` Server ready at ${url}`);
});
})();
为此,我可以 运行 graphql 查询 3000
上的服务器。
但是,通过使用石墨烯,尝试实现与 Ariadne 相同的功能:
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 响应 "data" 等于:
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没能成功获取到这个微服务schema的原因是什么?
该解决方案实际上是对通过 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:
这在联合中将服务组合在一起时很明显,因为它会引发错误:
GraphQLSchemaValidationError: Field "_Service.sdl" can only be defined once.
因此,尽管在定义 _Service.sdl
的微服务的完整模式中,我们希望 full-schema 的字符串的信息消失 returned 作为 return _Service.sdl
更新 2
Apollo Federation 现在运行良好,确保由 sdl
字段 return 编辑的字符串不包含联邦规范。
在石墨烯中,我认为每个实现可能会有所不同,但通常您要替换以下内容:
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,一个包含联合规范(针对服务 returned 的模式),另一个不包含联合规范(returned通过 sdl
字段)
您在另一个答案上的方向不错,但看起来您需要从印刷版中删除一些内容。
这是我在a github issue
中使用的方法我在这里总结我的代码:
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)
这个 pip 库可以提供帮助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 {
...
}
并且此语法不再有效,解决方法是使用 monkey-patch 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)
而且有效。