apollographql / federation-demo

Federation 2 supersedes this demo and this example is no longer the newest. See https://www.apollographql.com/docs/federation/ for migration steps!
https://www.apollographql.com/docs/federation/
MIT License
502 stars 198 forks source link

How to access additional data across federated services #1

Open MoonTahoe opened 5 years ago

MoonTahoe commented 5 years ago

Federation is great! I love it! This demo is also awesome, thanks! I do have one question about obtaining data from external services.

Let's say I work on the Accounts team. We're using Apollo Federation and have teams working on independent federated services. All of our teams are totally independent, we cannot request features from other teams. If we want to implement something, we have to do it on our own.

Our customers want a running total of the products that they have purchased. We have saved a list of upc codes locally for each purchase that a user has made. A user account record has the following data in our database:

{
  "name": "Jonathan Toews",
  "productsPurchased": [1, 2, 3, 2, 1]
}

So we are going to add a field that will resolve to the total dollars that a users has spent on all of the products:

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

However, when I get to the resolver I do not have enough information about the products that the user has purchased. I only have their UPC codes:

totalDollarsSpent: (user) =>
  user.productsPurchased
    .map(upc => /*Here I also need the price, but I don't have it*/)
    .reduce((total,product) => total + product.price, 0);

I'm not sure this is even a case for federation, but I was thinking about all of the use cases for teams who work in complete isolation. Federation gracefully handles every other use case that came up, so I though this may be in the ballpark.

jbaxleyiii commented 5 years ago

@MoonTahoe (👋 Alex!) This is a really great question 👍

The tldr; is this isn't supported right now 😭

The exciting answer is this is on the roadmap 🎉 This is what it would look like:

type User @key(fields: "id") {
  id: ID!
  name: String
  username: String
  productsPurchased: [Product] @internal
  totalDollarsSpent: Float @requires(fields: "productsPurchased { price }")
}

extend type Product @key(fields: "upc") {
  upc: String! @external
  price: Int @external
}

A couple things introduced here (that will come in multiple releases):

@internal directive. This will probably come first in the scheme of things. Internal will allow for hiding fields from the overall schema that the client can consume.

@requires support when used on a base type. This will come a little bit later because it complicates the query planner a good bit 😆. Ultimately the query plan would be something like this:

{
  me {
    totalDollarsSpent
  }
}

{
  me {
    id
    productsPurchased {
      upc
    }
  }
}

{
  ... on Product {
    price
  }
}

{
   ... on User {
     totalDollarsSpent
   }
}
{
  totalDollarsSpent: ({ productsPurchased }) =>
    productsPurchased.reduce(({ price }, prev) => price + prev, 0)
}
MoonTahoe commented 5 years ago

That is really cool. This is sort of how I was trying to use ‘@requires’ at first.

monoguerin commented 4 years ago

any updates in this?, looks like a feature that we will like to use in my team :)

jhampton commented 4 years ago

@MoonTahoe Thank you for bringing this to my attention. This is in the same vein as https://github.com/apollographql/apollo-server/issues/3621 and other concepts like "@scope", if I'm reading this thread correctly.

I wonder late into the evenings how far GraphQL's concerns should ultimately extend to all of these cases: perhaps it's the expressive, declarative, type-safe nature of GraphQL that causes us all to "lean in" to applying GraphQL to all patterns instead of insisting that its properties (expressive, declarative, type-safe) exist in other systems. Swagger/OAS helped to make great strides in REST, and these are all "echoes" of SOAP, CORBA, and similar attempts at ensuring that systems can be statically-analyzed, descriptive, and discoverable. JSON API and oData have some of these characteristics as well as an API layer. Traditional RDBMS's have always strongly-typed for their internal reasons and efforts to bring this into the ORM layer never really connected all of the dots (unless I'm missing a key implementation somewhere).

With all of this context, I'm going to add this to my list of considerations as we discuss the next steps on this kind of "scoping" of a single graph.

MoonTahoe commented 4 years ago

Thanks for looking into this @jhampton. Right now the primary solution for this requirement is to send a graphql-request from the resolver, which feels a little like stitching. I like the above solution that @jbaxleyiii presented. It looks like a sweet solution to merge that request in with the query plan as opposed to making an additional graphql-request from within a resolver.

wzalazar commented 4 years ago

I have the same problem, but with a Mutation that requires some extra data for checking some stuff, I tried to find some pretty solutions in the blog and the documentation, but I didn't find anything so far. In this case, we will decide to use graphql-request pointing to another graphql service. The question will be:

I will glad if someone Can add some inputs or experiences how we must resolve these situations

apro23 commented 3 years ago

Hello sir @jbaxleyiii is there an update on this one? thank you

saravind-hotstar commented 2 years ago

Please do if possible share the best practice in current spec to access additional data across services.

filwaline commented 1 year ago

If you're interested in how Apollo Federation can handle cross-service mutations, check out this repository Apollo-Federation-Mutation-Demo. It provides a practical example of accessing external data in a seamless way.

dudutou commented 1 year ago

It seems that the requires directive has been added and this issue has been fixed. https://www.apollographql.com/docs/federation/federated-types/federated-directives#requires