Apollo federation: Field/resolver that returns entities from two separate services that implement an interface - apollo-server

Summary
In GraphQL Apollo managed federation, I'm trying to figure out if it's possible for one service to have an aggregate field, that returns an array of entities two separate services. As in, I'm trying to support this use case:
query FromTwoServices {
aggregateField {
id
...on Service1Entity {
field1
}
...on Service2Entity {
field2
}
}
}
where both
type Service1Entity implements Aggregation
and
type Service2Entity implements Aggregation
And under the hood, the resolver for aggregateField (which lives on Service 2) calls down down to Service 1 to Service1Entitys. So this field is aggregating two data sets.
Example Problem
In my current GraphQL setup, I have all of this defined in one service:
interface Animal {
id: ID!
name: String!
}
type Cat implements Animal {
id: ID!
name: String!
meows: Boolean
}
type Dog implements Animal {
id: ID!
name: String!
barks: Boolean
}
When I query my service, it hits an animals resolver, and looks like:
query Animals {
animals {
id
...on Cat {
meows
}
...on Dog {
barks
}
}
}
Now I'm creating a Dogs service. I would like Dogs to come from the Dog service, and Cats to come from the Cats service. Additionally, I would still like to support my animals aggregation field, and I'd like to move it to the Dogs service.
To support this, I've made Cat an entity, and duplicated the interface across both services. To allow Dog service to resolve the Cat type, I've used "referencing" to tell Dog service that Cat lives somewhere else.
Cat Service (marking Cat as entity with #key):
interface Animal {
id: ID!
name: String!
}
type Cat implements Animal #key(fields: "id") {
id: ID!
name: String!
meows: Boolean
}
Dog Service:
interface Animal {
id: ID!
name: String!
}
type Cat implements Animal #key(fields: "id") {
id: ID! #external
}
type Dog implements Animal {
id: ID!
name: String!
barks: Boolean
}
The problem with this setup is the Dog service fails to start, because of this GraphQL error:
Error: Interface field Animal.name expected but Cat does not provide it.
In my Dog service, if I add an external name field, like so:
type Cat implements Animal #key(fields: "id") {
id: ID! #external
name: String! #external
}
Then the two services fail to compose in managed federation:
This data graph is missing a valid configuration. [dog] Cat.name -> is marked as #external but is not used by a #requires, #key, or #provides directive.
I sort of feel like I'm going down the wrong path here. Is it possible to have an aggregate field like this, where one service can return the its own entities, combined with the entities of another service?
We already have lots of clients querying for the animals field, so we're trying to support this change without requiring client changes, aka without restructuring the use of an interface.

Related

How to merge results from two gql queries in to one array of results?

We have two services exposing two sets of schemas, merged in a gateway using Graphql Tools Schema Stitching
Is it possible to merge queries from two services in such a way that it returns combined results?
Example case:
Book service contains data for books
interface Searchable {
id: ID!
}
type Book implements Searchable {
id: ID!
name: String
# other fields
}
type Query {
_search( term: String ): [Searchable]
}
User Service has the data for authors
interface Searchable {
id: ID!
}
type Author implements Searchable {
id: ID!
name: String
# other fields
}
type Query {
_search( term: String ): [Searchable]
}
Gateway
interface Searchable {
id: ID!
}
type Book implements Searchable {
id: ID!
name: String
# other fields
}
type Author implements Searchable {
id: ID!
name: String
# other fields
}
type Query {
search( term: String ): [Searchable]
}
I can recommend using GraphQL-Mesh - it uses tools under the hood, and enables you to easily merge multiple sources (GraphQL and many others), manipulate it and get one GraphQL endpoint / schema

Composite index in Dgraph?

type Species {
name: String! #id
animals: [Animal!]! #hasInverse(field: species)
}
type Animal {
name: String!
species: Species!
}
How do I make animal name unique within species? So that I can allow only one dog named Bob, but also one cat with the same name.
Apparently there is already a similar thread on the Dgraph forum: https://discuss.dgraph.io/t/composite-id-fields/13337

Using Apollo Federated GraphQL, is there a way to sort/paginate on a value from an external entity?

Is it possible to mark a field in an input to a query as requiring a field extended by a different service?
Using the federation-demo to illustrate my question, but keeping a little bit more simple. Having just an Account service and a Reviews service, and adding a karma field to the User, is it possible to filter reviews based on User karma.
Account service, adding a karma int to a User:
extend type Query {
me: User
}
type User #key(fields: "id") {
id: ID!
name: String
username: String
karma: Int!
}
Reviews service, adding a reviews Query:
extend type Query {
reviews(minKarma: Int): [Review!]!
}
type Review #key(fields: "id") {
id: ID!
body: String
author: User #provides(fields: "username")
product: Product
}
extend type User #key(fields: "id") {
id: ID! #external
username: String #external
karma: Int! #external
reviews: [Review]
}
extend type Product #key(fields: "upc") {
upc: String! #external
reviews: [Review]
}
In my resolver for Query.reviews, I want to filter out any review where review.author.karma is less than the specified minKarma.
How do I tell the gateway that when minKarma is specified in the Query input, I want the Account service to be queried first and a representation of users to be passed into the Reviews service, with the karma of each user attached to the review as the author, so that I can do the filter?
Circling back to the question at the top of this post, can I mark the minKarma field as requiring User.karma?
This is the questions plaguing me as well.

How to federate two apollo services that provide the same type

I am new to apollo and I have two apollo service that I want to federate by using apollo federation:
Productservice:
extend type Query {
job(id: String!): Job
}
type Seo {
title: String!
description: String!
keywords: String!
}
type Product #key(fields: "id") {
id: ID!
title: String!
seo: Seo!
}
StaffService:
extend type Query {
staffMember(id: String!): StaffMember
}
type Seo {
title: String!
description: String!
keywords: String!
}
type StaffMember #key(fields: "id") {
id: ID!
title: String!
seo: Seo!
}
How can I use the type Seo in response objects of both objects? Is the correct procedure to create an interface Seo and implement StaffMemberSeo and ProductSeo or is there an annotation that allows me to define the exactly same type within two services?
One service must own the type. In that service use the #key directive. In the referencing services use #extend, and include a stub of the type with fields used by that service.
Think of this as like a foreign key in a SQL database.

Connections, edges, node: how to use a subtype of a node

I'm following the Connection, Edges, Node, concept of relay in Apollo.
I use a Node { id: ID! } interface. I have a collection of entities that are all kind of 'groups', with small differences. I would like to model these as a single Connection, but not really sure how to do that:
# simplified for example
interface Group {
id: ID!
type: String!
}
type WallGroup implements Node & Group {
id: ID!
type: String!
}
type Event implements Node & Group {
id: ID!
type: String!
dteStart: DateTime!
dteEnd: DateTime!
}
type GroupEdge implements Edge {
cursor: String!
node: Group!
}
type GroupConnection implements Connection {
edges: [GroupEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
This will generate an error because GroupEdge expects node to a Node. It is, but the Group interface is not and it therefore fails.
Another thing I tried was
union AllGroup = WallGroup | Event
type GroupEdge implements Edge {
cursor: String!
node: AllGroup!
}
But this leads to the same issue. Apparently, the union loses notion of the Node interface implemented on WallGroup and Event.
Any ideas on hwo to model this or should I just duplicate everything?
The Relay specification only requires that your schema include a Node interface -- when creating a Relay-compliant, normally you don't create interfaces for Connection and Edge.
The reason Relay requires a Node interface is to allow us to query for any Node by id. However, typically there's no need for a field that returns one of many edges or one of many connections. Therefore, typically there's no need to need to make an interface for edges or connections. This is what you would normally do:
interface Node {
id: ID!
}
interface Group {
id: ID!
type: String!
}
type GroupA implements Node & Group {
id: ID!
type: String!
# other fields
}
type GroupA implements Node & Group {
id: ID!
type: String!
# other fields
}
type GroupEdge {
cursor: String!
node: Group!
}
type GroupConnection {
edges: [GroupEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
According to the spec, "the [implementing] object type must include a field of the same name for every field defined in an interface." So, if an interface specifies that a field is of the type Foo, an implementing type cannot have the field be of type FooBar even if Foo is an interface and FooBar implements it. Unfortunately, that means it's not really possible to use interfaces like you're trying to do.
If you would like to utilize interfaces to provide a safety-net of sorts and ensure that the implementing types are consistent, you can do something like this:
interface Connection {
pageInfo: PageInfo!
# don't specify edges
}
interface Edge {
cursor: String!
# don't specify node
}
This way you can ensure that fields like pageInfo or cursor are present in the implementing types, have the correct type and are non-null. But this kind of schema-level validation is really the only benefit you get out adding and implementing these two interfaces.

Resources