Федерация Graphql возвращает значение null при расширенном отношении

Я пытаюсь настроить прототип для использования graphql в нескольких микросервисах Java, что требует от меня объединения нескольких схем graphql в одну.

Я использую 2 java-сервиса и ApolloServer с ApolloGateway; который показывает следующую схему на игровой площадке:

type Client {
  id: ID!
  name: String
  linkeduser: User
}

type Query {
  user(id: ID!): User
  users: [User]
  client(id: ID!): Client
  clients: [Client]
}

type User {
  id: ID!
  name: String
}

При выполнении простого запроса:

query client {
  client(id: 1) {
    id
    name
    linkeduser {
      id
      name
    }
  }
}

Я ожидаю, что это вернет клиента со связанным пользователем; При отладке клиентская служба запрашивается, пользовательская служба запрашивается, но ответ:

{
  "data": {
    "client": {
      "id": "1",
      "name": "Bob",
      "linkeduser": null
    }
  }
}

Как мне получить в моем клиенте ответ пользователя по ссылке?

Я пробовал вернуть списки пользователей, новый клиентский объект со списком связанных пользователей, одного пользователя. Пример https://github.com/apollographql/federation-jvm является основой этого кода, хотя я еще не видел, чтобы это работало.

Код:

Услуга 1: Клиент


@WebServlet(loadOnStartup = 1, urlPatterns = "/graphql")
public class GraphQLService extends GraphQLHttpServlet {
    @Override
    protected GraphQLConfiguration getConfiguration() {
        return GraphQLConfiguration.with(getGraphQLSchema()).build();
    }

    private static GraphQLSchema getGraphQLSchema() {
        InputStream inputStream = client.GraphQLService.class
            .getClassLoader().getResourceAsStream("schema.graphqls");
        TypeDefinitionRegistry parse = new SchemaParser().parse(inputStream);
        RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
            .type("Query", builder -> builder.defaultDataFetcher(GraphQLService::getClient))
            .build();
        return com.apollographql.federation.graphqljava.Federation.transform(parse, runtimeWiring)
            .fetchEntities(env -> env.<List<Map<String, Object>>>getArgument(_Entity.argumentName)
                .stream()
                .map(values -> {
                    if ("Client".equals(values.get("__typename"))) {
                        final Object id = values.get("id");
                        if (id instanceof String) {
                            return getSingleClient((String) id);
                        }
                    }
                    return null;
                })
                .collect(Collectors.toList()))
            .resolveEntityType(env -> {
                final Object src = env.getObject();
                if (src instanceof Client) {
                    return env.getSchema().getObjectType("Client");
                }
                return null;
            }).build();
    }

    private static Object getClient(DataFetchingEnvironment environment) {
        switch (environment.getFieldDefinition().getName()) {
            case "client":
                return getSingleClient(environment.getArgument("id"));
            case "clients":
                return getAllClients();
            default:
                return null;
        }
    }

    //... extra code with simple getters
}

С этой схемой:

extend type Query {
    client(id: ID!): Client
    clients: [Client]
}

type Client @key(fields: "id"){
    id: ID!
    name: String
}

Услуга 2: Пользователь


@WebServlet(loadOnStartup = 1, urlPatterns = "/graphql")
public class GraphQLService extends GraphQLHttpServlet {

    @Override
    protected GraphQLConfiguration getConfiguration() {
        return GraphQLConfiguration.with(getGraphQLSchema()).build();
    }

    private static GraphQLSchema getGraphQLSchema() {
        InputStream inputStream = user.GraphQLService.class
            .getClassLoader().getResourceAsStream("schema.graphqls");
        TypeDefinitionRegistry parse = new SchemaParser().parse(inputStream);
        RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
            .type("Query", builder -> builder.defaultDataFetcher(GraphQLService::getUser))
            .build();
        return com.apollographql.federation.graphqljava.Federation.transform(parse, runtimeWiring)
            .fetchEntities(env -> env.<List<Map<String, Object>>>getArgument(_Entity.argumentName)
                .stream()
                .map(values -> {
                    if ("Client".equals(values.get("__typename"))) {
                        final Object id = values.get("id");
                        if (id instanceof String) {
                            return getSingleUser((String) id);
                        }
                    }
                    return null;
                })
                .collect(Collectors.toList()))
            .resolveEntityType(env -> {
                final Object src = env.getObject();
                if (src instanceof User) {
                    return env.getSchema().getObjectType("User");
                }
                return null;
            })
            .build();
    }

    private static Object getUser(DataFetchingEnvironment environment) {
        switch (environment.getFieldDefinition().getName()) {
            case "user":
                return getSingleUser(environment.getArgument("id"));
            case "users":
                return getAllUsers();
            default:
                return null;
        }
    }
    //... extra code with simple getters
}

С этой схемой:

type Query @extends{
    user (id: ID!): User
    users: [User]
}

type User @key(fields: "id") {
    id: ID!
    name: String
}

type Client @key(fields: "id") @extends{
    id: ID! @external
    linkeduser : User
}

Версия в POM.xml

<graphql.version>14.0</graphql.version>
<graphql-tools.version>5.2.4</graphql-tools.version>
<graphql-servlet.version>9.0.1</graphql-servlet.version>
<graphql-federation-support.version>0.4.0</graphql-federation-support.version>

1 ответ

Решение

В пользовательском сервисе вам нужно вернуть pojo типа client с получателем для связанного пользователя (должны присутствовать только поля extends):

if ("Client".equals(values.get("__typename"))) {
    final Object id = values.get("id");
    if (id instanceof String) {
        return new Client((String) id, getSingleUser((String) id));
    }
}

Также resolveTypeEntity необходимо разрешить указанному клиенту

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