federation icon indicating copy to clipboard operation
federation copied to clipboard

Router should support resolving interface implementations across subgraph

Open ramapalani opened this issue 3 years ago • 6 comments

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

ramapalani avatar Oct 14 '22 21:10 ramapalani

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.

gkesler avatar Oct 15 '22 21:10 gkesler

Likely a duplicate of #336

pfyod avatar Oct 25 '22 08:10 pfyod

@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

ramapalani avatar Oct 25 '22 11:10 ramapalani

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

lennyburdette avatar Nov 04 '22 14:11 lennyburdette

In https://github.com/apollographql/federation/issues/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).

pcmanus avatar Nov 24 '22 16:11 pcmanus

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?

jmccaull avatar Jul 05 '23 22:07 jmccaull