【问题标题】:Problems integrating Python graphene with Apollo Federation将 Python 石墨烯与 Apollo Federation 集成的问题
【发布时间】:2019-11-14 00:22:38
【问题描述】:

使用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)

现在,阿波罗联邦可以毫无问题地接受这一点:

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}`);
    });
  })();

为此,我可以在 3000 上对服务器运行 graphql 查询。

但是,使用石墨烯,尝试实现与 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: Type Query 必须定义一个或多个字段。

当我检查这件事时,我发现 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
}

以及使用石墨烯的微服务:

{
  "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 无法成功获取此微服务架构的原因可能是什么?

【问题讨论】:

  • 联合服务需要实现federation spec。在 Apollo 中,这是通过使用 buildFederatedSchema 函数来完成的。我不确定石墨烯supports anything like that.
  • 据我了解,在成功实现 Ariadne 之后,要使联合服务正常工作,架构中需要有一个 _service 字段,类型为 _Service,它具有一个字段sdl; whcih 将整个模式作为字符串返回。这很奇怪,因为这只是重复,本质上在模式中有一个字段,它返回所述模式。您是正确的,石墨烯本身并不支持这一点,但几乎每个后端都没有尝试使用 graphql,就像 Ariadne 我们只是定义他们的文档所说的需要。

标签: python-3.x graphql microservices apollo apollo-server


【解决方案1】:

如果有人想知道,这是因为 graphene 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)

它有效。

【讨论】:

    【解决方案2】:

    解决方案实际上是对通过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 网站上没有注意到的一句话是:

    此 SDL 不包括上述联合规范的新增内容。给定这样的输入:

    在联合中将服务组合在一起时很清楚,因为它会引发错误:

    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 字段返回的一个)

    【讨论】:

      【解决方案3】:

      这个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
      

      【讨论】:

        【解决方案4】:

        您在另一个答案上走在了正确的道路上,但看起来您需要从印刷版中删除一些内容。

        这是我在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)
        

        【讨论】:

        • .replace("type Query", "extend type Query") 没有错误吗?在此替换之后,当您扩展现在不存在的内容 (Query) 时,架构如何有效?当我开始工作时,我也会尝试一下..
        • 所以我刚刚尝试了您的解决方案,但仍然失败了联邦,因为它认为 sdl 的类型是 ServiceField,它应该是 _Service。我还在update 1 的回答中声明从sdl 返回的sdl 不包含联合元素,我现在将添加update2 以更清楚地记录这一点。
        • 你应该可以定义 SerficeField 的名字,对我来说没有问题。您实际上不需要替换扩展查询,它对我都有效。您需要的唯一联合元素是端点及其类型,您不需要定义指令。如果您查看我在此评论中链接的完整实现,我已经完成了整个工作:)
        • 这很好,但它仍然对我不起作用,我将接受我自己的答案,因为它确实解决了问题。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-05-11
        • 2022-01-24
        • 2020-06-24
        • 2017-05-13
        • 2018-01-11
        • 2020-09-01
        • 2019-12-20
        相关资源
        最近更新 更多