Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Router should support resolving interface implementations across subgraph #2193

Open
ramapalani opened this issue Oct 14, 2022 · 6 comments
Open
Labels

Comments

@ramapalani
Copy link

ramapalani commented Oct 14, 2022

GraphQL abstract types (Union, Interface) could be extended or implemented by various concrete types so a field declared to return a single instance or an array of abstract types can return any of those concrete types.
This works well when all implementation or component types reside within the same subgraph.
In the federated case any subgraph could extend union type defined in the other subgraphs or implement an interface defined in the other subgraph. Unfortunately, the router does not resolve all of the components/concrete types across multiple subgraphs, it only resolves the abstract types against the declaring subgraph.

Hence we are requesting to make the router to resolve abstract types against all subgraphs that register extensions or implementations of those.

Here is an example, let's say the interface Book has two implementations TextBook and ColoringBook.

Single Graph

If these interfaces were implemented in the same (sub)graph, a query on books would get results from both implementations.
Single Graph Schema

    interface Book {
      title: String!
      author: String!
    }
    
    type Textbook implements Book {
      title: String!
      author: String!
      courses: [String!]!
    }
    
    type ColoringBook implements Book {
      title: String!
      author: String!
      colors: [String!]!
    }
    
    type Query {
      books: [Book!]!
    }

Single Graph Query

query BookSupergraph {
  books {
    title
    author
    author
    ... on Textbook {
      courses
    }
    ... on ColoringBook {
      colors
    }
  }
}

Single Graph Query Response

{
  "data": {
    "books": [
      {
        "title": "Algorithms",
        "author": "Author Algos",
        "courses": [
          "CS-101"
        ]
      },
      {
        "title": "KG Coloring",
        "author": "Author Color",
        "colors": [
          "White"
        ]
      }
    ]
  }
}

Interface Implementations across subgraphs

Schema

TextBook Schema

  extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.0",
        import: ["@key", "@shareable"])

  interface Sub_Book {
    title: String!
    author: String!
  }

  type Sub_Textbook implements Sub_Book {
    title: String!
    author: String!
    courses: [String!]!
  }
  type Query {
    sub_books: [Sub_Book!]!
  }

ColoringBook Schema

  extend schema
  @link(url: "https://specs.apollo.dev/federation/v2.0",
        import: ["@key", "@shareable"])

  interface Sub_Book {
    title: String!
    author: String!
  }

  type Sub_ColoringBook implements Sub_Book {
    title: String!
    author: String!
    colors: [String!]!
  }  
  `);
  // Having sub_books in query will not compose as it is defined by Textbook subgraph.
  // type Query {
  //   sub_books: [Sub_Book!]!
  // }

Query

query BookSupergraph {
  sub_books {
    title
    author
    author
    ... on Sub_Textbook {
      courses
    }
    ... on Sub_ColoringBook {
      colors
    }
  }
}

Response

{
  "data": {
    "sub_books": [
      {
        "title": "Algorithms",
        "author": "Author Algos",
        "courses": [
          "CS-101"
        ]
      }
    ]
  }
}

Asks

  1. ColoringBook subgraph should have the ability to say that it could resolve interface Sub_Book
  2. Router will have access to all implementations of the interfaces across subgraphs and can query these subgraphs separately and merge(flatten) the contents.

A working example is available here: https://stackblitz.com/edit/basic-federation-2-hsrxlr?file=two.js,three.js,one.js (Use Chrome)
image

@gkesler
Copy link

gkesler commented Oct 15, 2022

The list of subgraphs against which the router should resolve abstract field could be constrained by the fragments used in the query to apply on the abstract field results. For instance, continuing with the example above, if the query looks like:

query BookSupergraph {
  sub_books {
    ... on Sub_Textbook {
      title
      author
      courses
    }
  }
}

the router could resolve field sub_books only against TextBook subgraph ignoring the Sub_ColoringBook one since the client did not request any fields from the Sub_ColoringBook implementation type.

@abernix abernix transferred this issue from apollographql/router Oct 17, 2022
@pfyod
Copy link

pfyod commented Oct 25, 2022

Likely a duplicate of #336

@ramapalani
Copy link
Author

@pfyod There is a possibility that a fix for #336 might fix this one too. In #336, implementation of an interface is not available in a subgraph, here I'm trying to implement an interface in two different subgraphs.

Usecases are slightly different. So I would keep this open

@lennyburdette
Copy link
Contributor

I added a technote to our docs covering this topic: https://www.apollographql.com/docs/technotes/TN0018-aggregating-across-subgraphs/

@pcmanus
Copy link
Contributor

pcmanus commented Nov 24, 2022

In #336, implementation of an interface is not available in a subgraph, here I'm trying to implement an interface in two different subgraphs.

For what it's worth, I created #2277 as a dedicated issue for the proposal I laid out in my comment on #336. It is true that the first iteration of that proposal will not tackle the question of distributing the implementations of an interface over multiple subgraphs, but I do discuss that question in some details in the last sub-section of the description on #2227 (the one named "Potential future followups" ).

That said, the tl;dr is that doing this in general means that, as you yourself mention above ("Router (...) can query these subgraphs separately and merge(flatten) the contents"), the router may have to query all the subgraphs having implementations of the interface. And while I get both that 1) this may be fine when you known that you have only 2 subgraphs involved and 2) very specific queries could be done more efficiently than that, it is also something that feels hard to use right and doesn't scale very well. In fact, I could even argue that this goes against some of the the main goals of federation, because:

  1. federation is ultimately about allowing to scale the development of large graphQL APIs. So we want to be extra careful introducing feature/behaviour that scale poorly.
  2. the use of federation is meant to be as transparent as possible to clients of the federated API. Having to known exactly how the underlying subgraphs are laid out to be able to craft efficient queries is also something we want to avoid.

Anyway, don't mean to completely shut the door on some support for this ever, but I want to be upfront that the current thinking is that it's a better approach to use a specific subgraph to do any kind of non-trivial dispatch (what @lennyburdette describes in his technote in the previous comment).

@jmccaull
Copy link

jmccaull commented Jul 5, 2023

I came across this while trying to figure out an approach to simplify a number of our subgraphs and wanted to suggest that maybe a limited scope of this could be supported, possibly with distinct directives that allowed a narrow composition definition. Our specific use case involves a non uniform distribution of subgraphs handling different markets for similar types. A simplified example would be something along the lines of:
subgraph 1

type Query {
  usAssociate: USAssociate
}

type USAssociate implements Associate @key(fields: “id”) {
  id: String
  ssn: String
}

interface Associate @key(fields: “id”) {
  id: String
}

Subgraph 2

type Query {
  jpAssociate: JPAssociate
}

type JPAssociate implements Associate @key(fields: “id”) {
  id: String
  personalIdentificationNumber: String
}

interface Associate @key(fields: “id”) {
  id: String
}

Subgraph 3

type Associate @key(fields: “id) @interfaceObject {
  id: String
  schedule: Schedule
}

type Schedule {
…

In this case, as a matter of governance, we wouldn’t necessarily have any subgraph use the interface as a response type, but as a method to simplify extensions for subgraphs that cross market boundaries. Are there any considerations here I may be missing? Maybe this just isn't in the spirit of what interfaces are intended to solve?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants