apollographql / apollo-client-nextjs

Apollo Client support for the Next.js App Router
https://www.npmjs.com/package/@apollo/experimental-nextjs-app-support
MIT License
436 stars 32 forks source link

How to use SSR and RSC for multiple GraphQL servers? #343

Closed Tsimopak closed 1 month ago

Tsimopak commented 1 month ago

I have a multiple micro services that each one of them expose different graphql api, how can i use them using this package API? should I make two ApolloWrappers? If so, would it mess up with the cache system? How can I decide when using query from which server it should try to request the data from?

jerelmiller commented 1 month ago

Hey @Tsimopak 👋

Unfortunately using multiple clients to stream data isn't a pattern that is supported in this library, nor do we have plans to support this at the moment. While you'd be able to instantiate multiple clients on the server, the streamed responses aren't guaranteed to hydrate into the right client instances in the browser, so you'd end up with a mess of data in your client instances in the browser.

One option might be to only run these queries in the browser and avoid running them in SSR to prevent streaming from kicking in.

If you're ok with data being mixed from each GraphQL API, its possible to use a single Apollo Client instance that writes into the same cache. Use context in this case so your query fetches from the right GraphQL endpoint. HttpLink has the ability to read the uri from context, so this would give you control for an individual query to specify where to fetch it from.

useSuspenseQuery(QUERY, { context: { uri: "https://endpoint-a.com/graphql" }})

I just want to highlight a danger here though in case you want to consider this route. Overlapping __typenames between the different GraphQL APIs have the potential to overwrite each other in the cache, especially if they happen to share the same ids. I'd be really cautious and check for this in case this would present a problem for you.

The best option would be to write your own GraphQL schema that is able to consolidate the multiple APIs into a single schema and query against that. I don't know if you have control over those other GraphQL APIs or not, but Apollo federation might be an option for you if you do.

Tsimopak commented 1 month ago

@jerelmiller

Thanks for your response! Ohh dang it haha I really hope you guys would consider to add it as a feature I feel like it's really great, I know that in rest api its really common to decouple between micro services completely.

Ok so moving to your solution,

If I'll change my the type names of my other service, and use all of my micro services under the same apollo client instance, you don't see a problem doing so with the context you suggested?

Can you elaborate what does it mean to run the queries in browser? I thought that there are two methods of using this library:

  1. RSC
  2. Client components with streaming SSR

I assume you are talking about the 2nd method? so Essentially I should follow this example?

"use client";
// ^ this file needs the "use client" pragma

import { ApolloLink, HttpLink } from "@apollo/client";
import {
  ApolloNextAppProvider,
  ApolloClient,
  InMemoryCache,
  SSRMultipartLink,
} from "@apollo/experimental-nextjs-app-support";

// have a function to create a client for you
function makeClient() {
  const httpLink = new HttpLink({
    // this needs to be an absolute url, as relative urls cannot be used in SSR
    uri: "https://example.com/api/graphql",
    // you can disable result caching here if you want to
    // (this does not work if you are rendering your page with `export const dynamic = "force-static"`)
    fetchOptions: { cache: "no-store" },
    // you can override the default `fetchOptions` on a per query basis
    // via the `context` property on the options passed as a second argument
    // to an Apollo Client data fetching hook, e.g.:
    // const { data } = useSuspenseQuery(MY_QUERY, { context: { fetchOptions: { cache: "force-cache" }}});
  });

  // use the `ApolloClient` from "@apollo/experimental-nextjs-app-support"
  return new ApolloClient({
    // use the `InMemoryCache` from "@apollo/experimental-nextjs-app-support"
    cache: new InMemoryCache(),
    link: httpLink,
  });
}

// you need to create a component to wrap your app in
export function ApolloWrapper({ children }: React.PropsWithChildren) {
  return (
    <ApolloNextAppProvider makeClient={makeClient}>
      {children}
    </ApolloNextAppProvider>
  );
}

Or use this example: https://github.com/apollographql/apollo-client-nextjs/blob/main/examples/polls-demo/app/cc/poll-cc.tsx

?

Thanks in advance again! :D

jerelmiller commented 1 month ago

I probably should step back a bit. I assumed you were referring to client components in your original issue description, but now I'm questioning if I read it wrong 😆. Are you running these queries in server components or client components?

If server components, multiple clients should be ok here simply because these clients are never instantiated in the browser and the cache is never shared with your client components. The caches for these clients only exist for the duration of the request.

If client components, then everything I've said still applies since we stream cache data from your client instances from the server to the browser as data is loaded.


Ohh dang it haha I really hope you guys would consider to add it as a feature

Its extremely complicated to develop a streaming solution that is able to hydrate streamed data from specific client instances on the server to specific client instances in the browser since you're passing over the network boundary.

Can you elaborate what does it mean to run the queries in browser?

I just mean that you can detect if you're running in the server/browser and only render your query components if they are rendered in the browser. One trick to get this to work is to use useSyncExternalStore to detect when you're on the server. Check out this blog post for a technique here.

The downside here is that you lose out on the benefit of loading that data on the server and have to wait for the page to be hydrated before those queries are kicked off.

That said, using multiple clients only in the browser is ok from our perspective because there is no streaming involved here.

If I'll change my the type names of my other service, and use all of my micro services under the same apollo client instance, you don't see a problem doing so with the context you suggested?

I'll be cautious in saying that I "don't see a problem" because you may very well face some interesting challenges. I'm just saying that having unique type names across your different GraphQL APIs helps alleviate a potential issue where using a single client with multiple GraphQL endpoints could end up merging data from each endpoint into a single object record in the cache. This is problematic because data could accidentally overwrite each other that should otherwise be siloed.

Again, I'd recommend taking a look at Apollo Federation as a way to create a unified supergraph out of your underlying subgraphs. GraphQL is best used as a single unified API. I obviously don't know your architecture though and am sure you have great reasons for splitting up your GraphQL APIs into several microservices. I'd just advise taking a look at tools like Federation that help with this kind of architecture across several GraphQL microservices.

Hope this helps!

Tsimopak commented 1 month ago

Thank you so much for the detailed answer, I'll keep this thread open a bit longer for future questions i might have

Tsimopak commented 1 month ago

@jerelmiller About your suggestion to use Federation, a fundamental draw of federation is avoiding Database per service, which means, there's dependency between services, which in my case some services may be down at any given time.

So federation is not an option for me, I could use federation just to wrap all graphql servers together, but what if the gateway will go down? i prefer that each service will be responsible for its own data, I could do it purely to work with this package, other option is to fallback to RESTful api or RPC, but I think that GraphQL is still beneficial even without federation

jerelmiller commented 1 month ago

Just to clarify, I'm not saying you have to use federation, I'm just suggesting that it might be a nice tool for your situation in case you weren't aware of it. You're best qualified to figure out what is the optimal server architecture for what you're building, so respectfully, I'll bow out of that part of the conversation.

Let me know if you have any other questions, otherwise I'll go ahead and close this issue 🙂

Tsimopak commented 1 month ago

Thanks jerelmiller for the help :D No issues so far i'll open a new ticket if i'll get one. thanks for the approaches u suggested

github-actions[bot] commented 1 month ago

Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Client usage and allow us to serve you better.