Thank you to everyone who attended my webinar. It was humbling to see so many people show up and I wanted to show you my gratitude. I took every question that was asked in the webinar and thought it would be good to make a post answering them all. I did not filter/edit the question and ommitted last names since you can’t see those in the webinar. (also I wrote all this in vscode markdown and pasted into forums, good luck everybody!)
Edit: I should also add a link to the recording and a link to the code repo that we used.
Javier - N+1 is one of the most common issues faced by thinking in a rest-y way when getting into graphql. Batching is one way to tackle it, and cashing too. Is there another recomendation from your side to work around it?
Dataloader, custom batching mechanisms and caching are the most common approaches to dealing with this. Most commonly this problem occurs from an underlying datasource in the GraphQL server returning everything. Think back to our API example around Orders
where the json
response also contained all the product/item info associated with each order? This will most likely naturally create an N+1 until that underlying service is destructured. In this example, there is a REST endpoint that is aggregating multiple data sets together. Another option would be to integrate directly with those data sets through a microservices strategy. A lot of teams use GraphQL to access their microservices.
Jeff - will this recording be available later?
Andre - Will this recording be publicly available? (where, if yes) Thank you!
Andre - Super, thanks again!!
Yup! Here it is
Efren - Can you start out with one server and a monolithic but have different subgraphs? I know by experience that the graph gets big really quick and is hard to manage in a single file.
Ya definitely and I’ve seen companies do this in the past where they have a GraphQL monolith with their “subgraphs” broken into folders. This was a couple years ago before Apollo Federation came out, but they were happy and very successful with that pattern. It does come with it’s own challenges when enough developers start contributing to it (as with any monolith) and good internal documentation becomes a requirement.
As for the details of how to do this, ApolloServer
can be provided a schema
instead of the typeDefs
and resolvers
directly. You can use various OSS tools out there like graphql-tools
merge multiple schema files together. As with each solution, they have their own unique challenges. In this specifically, teams have had scaling issues around merge conflicts and how that gets handled. There is a lot of materials online around this that I would recommend you search a bit for.
Rahul - can u please point to security — mainly around data authorization based on user profile and how it can integrate to data governance tools
I would recommend thinking about your GraphQL layer as a thing “routing” layer into the datasources of your business. The GraphQL layer will have an “onion architecture” built around it and typically one of those layers is an API management tool that provides things like Auth/Identity, Rate Limiting, Enterprise security stuff etc. Most teams have the assumption (or internal security team requirement) that the incoming context
for a GraphQL operation will already have this information validated and provided in an expected format. Then it’s about taking that incoming header information and using it in the downstream services appropriately. Some teams also just pass the auth headers through to the downstream services when they first start with GraphQL. Head over to our docs to learn more.
SASIDHAR - If we break into multiple mutations , when user can update all teh field in one submit, will it be aoverhead ?
It’s hard to say because each situation will be unique, but in our example we’re assuming that the Order
update in the API is the same work for changing 1 field or all of them. This is a great example of the con in exposing a simple update${TypeName}
in your Mutation
; it’s hard to tell the difference between updating 1 field and udpating all the fields. In my experience, this is never a problem until 1 day the team adds a field to the Order
that doesn’t live in that same datasource and then shoved into the updateOrder
. Once it goes live, all of the sudden the monitoring tools start alarming because the updateOrder
mutation the releasing client team is sending is taking longer to load. I hope you’re logging the incoming input arguments otherwise you’re not going to see this happen.
It’s a 0 cost to simply add multple fields into your Mutation
. I recommend breaking out the update${TypeName}
to be more specific to the capability/functionality you’re trying to drive. The goal isn’t to make a giant graph, it’s to make a graph that establishes your company as a platform. What does that mean? It means when that next thing comes out, your internal developers can easily integrate it into your business’s platform. Hey this new glasses thing came out and we can make an app for it, wouldn’t it be cool if I can get a quick order status update? Good thing it’s already exposed on your companies graph. Hey this other company/partner wants to pay us to integrate a capability we offer into their offering That is now a very viable option even if you just host your graph in a new environment and slim it down to only the desired capabilities (hint, Apollo Federation can create some very interesting solutions when you think about it)
SASIDHAR - Can we have some samples of one type extending another type - inheritence ? currently we are using interface, but it will result in repeating same multipe time, if we have to change a name of oen field we have to update all of them
Ya it sounds like you don’t want to be using an interface here. Can you reply in this thread and ping me with some more details? It would be good to have an example of the type, how it’s being extending, what you see the interface accomplishing and what an example field addition might be. Those details will help me put together a better answer that others will be able to benefit from
Daniel - "Is Apollo Studio HIPPA compliant and hardware? This has been the challenge adopting the tool even though it’s an RPM tool like APM tools NewRelic. We would love to have logging leveraged and tracing and so forth.
Nice to hear you again ~Daniel"
Great to hear from you again also Daniel! Unfortunately we’re not HIPPA compliant and although it has come up in conversations, we don’t have immediate plans to take that journey. I’m sure one day we will, but we’re still growing and there is a lot we still have to do.
BUT… I think it’s really important to note the developments that have been happening in OpenTelemetry. Really we think of Apollo Studio as the usage statistics of how your graph and using that information to power descisions around managing your schema (like validating a schema change against production use). Just recently we release OpenTelemetry support in Apollo Federation with Apollo Server and even have some nice docs on it! I’m planning on getting to a blog post on it, I just have a lot of things cooking. Hopefully the OpenTelemetry stuff is helpful for your future!
Rahul - any thought of integrating with OPA?
I have been looking into various things around federated services deployed in cloud native environments. Specifically deployment strategies for federated architectures and controling traffic between the router and subgraphs. This is right up in that alley, I’ve added it to my internal doc; I have a lot of things cooking.
Juan - Any pattern or recommnedation to implement something like custom JSON Schema that alow you to expose something like validations to clients? (e.g. regex for a field that we want our client to use)
A lot of teams use custom scalar
types to have the validation on a field and I’ve seen a JSON
scalar in a lot of graphs before. Honestly though, let’s talk a little bit more about the validation you’re trying to expose to the client and the behavior you want to drive.
I want to offer validation help to the clientd developers so it’s easy to send the right inputs
First you need to make sure your inputs make sense and you have properly added comments to your GraphQL schema. This will show up in their dev tooling and provide that context. Custom scalars vs the GraphQL built-in scalaras will help signify that this things is different than a String
. One common place I hear is about Date
and having the right format for a Mutation
. So a Date
scalar is made that contains code for that validation and throws the appropriate errors based on that.
I want to provide validation errors to my graph consumers
You may not want to just throw errors in your custom scalar
because it’s just going to show up in the errors. No client developer actually wants to do this:
const { data, errors } = useQuery(SET_START_DATE);
if (loading) return 'Loading...';
if (errors) {
//How do I start dealing with the validation errors?
//Why do I need to think about this so much? Can't I just have the validation errors in an easier way?
//Is it errors.forEach(error=>{ if(error.extensions[0].validation) ..........I don't know.....})
}
return (<WhatIActuallyWantToWorkOn value={data});
they really should at leas have an experience like this:
const { data, errors } = useQuery(SET_START_DATE);
if (loading) return 'Loading...';
if (errors) return (<UhOhUI/>);
if(data.myMutation.validationErrors) return (<InvalidInputUI errors={data}/>);
return (<WhatIActuallyWantToWorkOn value={data}/>);
There is more we can go on with, but I think this gives ome exampels of a minimum of what I would try to provide. Tooling should give a lot of this context, a big thing we have with the Apollo Platform.
Tapan - There are situations where in a query some parameters are computed faster and other parameters are header to compute and take longer ? Is there any way to handle this situation ? Defer attribute is not yet available in graphql-java ? Also, How does apollo federation handle this situation ?
Great question, @defer
and @stream
are commonly asked from the community. We are working towards this! But it will take a lot more than just the client side so more to come in the future.
Corey - "Error handling in GraphQL is a huge challenge. For example, say you follow this pattern where you update an accounts “firstName” and “email”.
Given that an email is unique in a system, what if updateEmail
fails, but the updateFirstName
succeeds? I know ApolloServer is returning an array of GraphQL Errors, but this is a unfamiliar pattern to me for handling one big form (OBF). Typically in a RESTful way of just calling updateAccount
, you fail the endpoint and expose the validation error"
Yeah I actually never like using the errors
portion of the GraphQl response and try to hide a lot of that. At Apollo, we just return null
for everything when there is an error to make it simple. I think whatever you’re returning to the consumer should be formatted to what you think would be helpful and cutting everything else out.
Nir - Do you see the federation gateway as an edge gateway only? How would internal requests that query federation entities work? Through that same edge gateway?
We don’t currently, but we do a lot of rust work and hope to one day have the router as a binary available.
Daniel - What do you think of Hasura’s setup (sorry if they are competitor), their way on plugging into a DB and their row and column permission system?
Daniel - very disappointed that this question was hidden an unanswered
I’m sorry Daniel, I truely did not see this question. When I switched to the second round of question I just saw a giant empty space in the scroll bar and figured I would scroll to a random spot and start from there. Happy to give my answer here and I pride myself on not avoiding the hard questions (or follow ups to that question if you ping me in a reply
).
I don’t see Hasura as a competitor (or any other GraphQL database), I see it as another GraphQL tool in your toolbelt. Any GraphQL database can just be one of the GraphQL datasources in your federated graph even if it doesn’t support the directives of Apollo Federation. If it has a GraphQL spec compliant schema and an endpoint that you can send GraphQL requests to, it can work in a federated graph. I’ve also been on a call with a graph database company very recently in helping discuss how they can support the Apollo Federation directives (which they have working).
Also Hasura is a good product! I’ve seen and heard great things. There are a lot of applications out there that would be just find using only a GraphQL database, but in my experience, large scale applications tend to need more to address the scale. Caching layers and various orchestrations mechanisms are needed which would could still use that GraphQL database as one of the inputs.
Think about a company trying to create a graph for their organization to transform into a platform. I don’t think anyone would recommend to just use a GraphQL database for everything (no one recommended doing that with SQL either). There will be multiple teams that provide capabilities in the organization and they be established in unique ways. One team may be analyzing a data set with some machine learning and then pushing the dataset into a Hasura instance. Another may be powering the companies main website and has their GraphQL server written in Java; they generally reject using anything other than Java (this is common). How does the company bring it all together? This is a larger question that we at Apollo think is solved with Apollo Federation. There are also other options which each have their own pros and cons. The edge I fall on is that you can pretty much never say this won’t work in GraphQL because there for sure is some developer out there that did that. I always think I’ve seen it all and then I talk with another dev doing some wild thing that blows my mind.
If I was going to start up a new app on the side myself, Hasura is a pretty great option for that. I do like writing server code and generally decent to bad at UI so I end up coming back to Apollo Server as my default .
Andre - Would you point one or more public examples of a well designed GraphQL APIs?
As I mentioned in the webinar, any publically available GraphQL API is doing something right. I mean they have their business out there successfully and that is a huge accomplishment with some great learnings that can be extracted. With that said, here are some publically available graphs (with docs) I commonly recommend to check out:
Corey - Could you speak on database transactions + GraphQL? If you run multiple mutations, they are running in parallel, so what have you seen work for designing an ACID compliant system using graphql?
This is a very interesting, I just had a conversation with the lead at StockX about their setup. Typically they go with an Atomic mutation pattern and keep things very very simple. update${TypeName}
is common for them and that maps directly to the transaction. This has been successfuly for them as they have scaled.
SASIDHAR - if we want to update multiple comments then end consumer has to call both mutations correct ?
If you ever have a situation where a consumer is having to make multiple mutations, this is a hint that you should add a new capability to your graph that combines those two functionalities. You could expose an updateMultipleComment(comments: [CommentInput])
and batch that accordingly. It’s up to you to decide how you want that functionality to be used, it could also be that they are updating multiple comments for another reason (not that they actually want to be updating multiple comments). I don’t know your exact situation, just some thoughts.
Farzaneh - Doesn’t breaking down the api data into smaller chunks based on what we care increase the traffic (number of trips) over the network? I always consider number of requests to the api when I create my schema and query.
I’m going to make a diagram for this in the future, but it definitely can increase the number of trips over the network depending on how your API is strucutured. In our example we had the Orders
API returning all of the product information. If we broke it down into a Products
API and had the Orders
API only return the product.Id
, then you would be adding n calls to the Products
api per Order
api call. But here is the thing, that Product
API has a lot of cachable data in it. How often is the name, description, other fields actually changing? Even adding a caching time-to-live (ttl
) of 60 seconds can drastically change the performance.
Jaycob - What does versioning look like?
Try to think of versioning as a v1 and then vNext forever. Your schema should be fluid and be able to change as your business evolves.
Daniel - I think the question was handling authorization not authentication
You could be right! So let’s talk about handling authorization. Authorization is determining what a given user has permission to do or see and some teams think about this as scoping the graph of what they can see. I recommend not having introspection on in production if you don’t have a public GraphQL API, so you don’t need to worry about changing the schema based on the permissions someone has. You’ll need to built this layer either into the resolvers directly or model it into your schema. Some teams create an @auth
directive that has certain requirements to be executed, but it can get a little messy if you have a query with multiple root fields that have different levels of @auth
. You can also model this into your schema which is something similar to what we do at Apollo.
For example, in our schema we have api keys granularity for graphs:
type ServiceRoles {
canCheckSchemas: Boolean!
canCreateVariants: Boolean!
canDelete: Boolean!
canManageAccess: Boolean!
canManageIntegrations: Boolean!
canManageKeys: Boolean!
canManageVariants: Boolean!
canQueryCheckConfiguration: Boolean!
canQueryDeletedImplementingServices: Boolean!
canQueryImplementingServices: Boolean!
canQueryIntegrations: Boolean!
canQueryPrivateInfo: Boolean!
canQueryPublicInfo: Boolean!
canQueryRoleOverrides: Boolean!
canQuerySchemas: Boolean!
canQueryStats: Boolean!
canQueryTokens: Boolean!
canQueryTraces: Boolean!
canRegisterOperations: Boolean!
canStoreSchemasWithoutVariant: Boolean!
canUndelete: Boolean!
canUpdateAvatar: Boolean!
canUpdateDescription: Boolean!
canUpdateTitle: Boolean!
canWriteCheckConfiguration: Boolean!
}
and then we have logic in our resolvers accordingly. I think some don’t like this pattern because of how many permissions they might have. I definitely don’t recommend copying the permission set from your CRM for example, but some app use cases require things like that and something else might be needed.
Javier - can you share the project with us to recreate locally step-by-setp what you’re showing? <3
Yup, here it is. I’m getting this post live so it’s in our follow-up and then I’m going to tighten the code up into a better workshop format. I saw some feedback about doing a live coing from start to finish, looking into doing a live stream of that in the near future. Well maybe looking into it Monday :allthings:
Daniel - We use OAuth2 for most services where it’s possible and cost effective assuming your licensed with OAuth inheritance for all your objects your accessing
Ya and you can just path that auth token around your services as needed also.
Juan - if you are building a new platform from scratch, would you architecture the graphql layer calling REST endpoints, or would start with the graphql layer calling directly services interfaces (it is graphql deployed within the service layer) ?
I think it depends on what your REST endpoints actually are. If they are BFF style orchestration layers with underlying datasources, well it might be easier to just connect to the underlying datasources as needed. You might have some internal politics that may make that challenging and another architecture could be more desireable.
The easiest and quickest option is to just connect directly to the REST endpoints you have existing today and drop all the data that won’t be used (meaning don’t expose all REST fields as GraphQL fields). Once you establish the graph in production, you can then turn your focus to destructuring the REST endpoints and connect directly to the services based on the performance of your graph. I typically recommend adding caching/performance stuff as needed, you can easily go overboard on that.
Callum - Can you tell apollo-datesource-rest to not cache on a specific request through these options?
Yup you can like this:
this.get(orderId, {}, { cacheOptions: { ttl: 0 } });
You just may need to take some incoming header from the client request like do-not-cache
and then pass that down into the datasource to set the ttl
.
Javier - what’s the benefit of passing the API functionallity through the context instead of importing a separate dependency?
I think an example would be helpful in me understanding the API functionality you’re thinking of pushing through the context. Let’s take a swagger doc for example, there is a lot of things in there and I wouldn’t say you want to have everything mapped into the context. Keep it strictly to what you need on the context and avoid any async
operations that are run on every request when you can.
Vaibhav - What VSCode extension should I use so that when I command click on a graphql fragment for example and it shows me its definition, (and other intellisense stuff)?
:grimmacing: I have not implementing the right click functionality in Apollo Workbench, it’s one of the items on my list but also a harder thing. The standard GraphQL
community extension is a good one but I don’t think it has everything you’re going to hope for with right click.
Abhijit Suvarna - How shoud the resolvers look when the filed needs to be a combination of two different services. E.g if we take your example of Order and Detail being in different services and suppose order.trackcode = order.id + detail.id . Pushing this merging onto the client only adds complexity in the client .
Wouldn’t this actually be detail.trackcode = order.id + detail.id
?
I would expose that as an additional field somehow that then uses those parent items to f. This becomes simpler in Apollo Federation to be honest:
Orders Service
type Order @key(fields:"id"){
id: ID!
number: String
customerComment: String
internalComment: String
details: [Detail]
}
extend type Detail @key(fields:"id") {
id: ID! @external
order: Order
trackCode: String
}
Details Service
type Detail @key(fields:"id") {
id:ID!
}
In the Orders service, now you have a resolver for `Detail
Jeff - thanks. This is awesome Mike
Thanks Jeff, I appreciate it