使用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)),
]
,...现在导致来自阿波罗联盟的错误:
As I checked into this matter, I found that apollo calls the microservice with a graphql query of:
query GetServiceDefinition { _service { sdl } }
Running it on the microservice via Insomnia/Postman/GraphiQL with Ariadne gives:
{
"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
}
and on the microservice with 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
}
So, they both are the same thing for defining how to get sdl
, I checked into the microservice response, and found that graphene response is sending the correct data too,
with the Json response "data" being equal to:
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')]))])
So what could the reason be for Apollo Federation not being able to successfully get this microservice 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
}
我没有在Apollo网站上注意到的一句话是:
该SDL不包括上述联邦规范的增加。给定这样的输入:
在联合中将服务组合在一起时,这很明显,因为它将引发错误:
GraphQLSchemaValidationError: Field "_Service.sdl" can only be defined once.
因此,尽管在具有define的微服务的完整架构中_Service.sdl
,我们希望该信息消失在完整架构的字符串中,该字符串作为以下项的返回字符串返回:_Service.sdl
阿波罗联盟现在可以正常工作,并确保该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
字段返回)。