GraphQL Operations’ Conventions
When GraphQL APIs start to grow, conventions are needed for both your team developing the API, and for your consumers, the front-end and your clients.
There’s no official standard on name conventions so I’ll show some options I’ve seen out there, and you can choose the one that fits your case the best!
Queries
Queries are pretty similar among the APIs, they refer to the first-class citizens, the main entities in the API or the aggregate roots of the domain; or actions that don’t mutate the data, like search
.
Example queries (taken from Github’s API):
The pattern would be something like:
Entity (...parameters) : Result
Action (...parameters) : Result
Note: input
parameters aren’t generally used in popular APIs but they can be used without any problem. If you think that the parameters of your query can be reused or will grow, go for them.
Mutations
Firstly, mutation parameters and results usually follow the input/payload pattern as follows:
# mutation (InputType) : PayloadType
updatePersonAddress(UpdatePersonAddressInput): UpdatePersonAddressPayload
Secondly, Specificity is very important. Make mutations as specific as possible. Mutations should represent semantic actions that might be taken by the user whenever possible.
Thirdly, name conventions. I’ve seen 4 approaches, these are the patterns (names made up by me):
1. VerbNoun
Pattern: <verb><noun>
Examples:
setRepositoryOwnerEmail
setRepositoryOwnerName
updatePersonAddress
This is supposedly how Facebook uses (see Lee Byron’s comment) and what Apollo recommends.
2. Namespaces
Pattern: <namespace><action>
Examples:
repositorySetOwnerEmail
repositorySetOwnerName
personUpdateAddress
Namespaces has been discussed for a while now for the spec in this GraphQL official Namespaces RFC, but they haven’t been convincing the right people yet. These are some of the pro’s/con’s seen in the discussion.
PROs
They
- Provide a sense of organization to the API, helping on the discoverability of the actions
- Avoid name clashes
- Easier stitching of different schemas (see
Cons
for the dark side of this)
But we also mentioned some possible risks that should be avoided.
CONs
- It might help stitch different schemas/sources without any API unification, it would be just gluing downstream APIs,
each with its own namespace. This triggers a couple of questions you’ll need to answer:
- Should your API represent one domain or more? (one is usually the right answer)
- Are you leaking implementation details to the API? (no is usually the right answer)
- It could be used to version parts the API (e.g.
namespaceV2search
), which isn’t the graphql way; namespacing is not versioning - It could be used to duplicate types, like
Accounting/Person
/Marketing/Person
. This can break the single domain of the API or can allow bad DDD - Reuse of types is encouraged to simplify the API and avoid the proliferation of same but similar type of types/operations. NS leaves the door open for repetition. Look for reuse opportunities and evaluate if they make sense
Just to be clear, this is a valid approach, you just need make sure it doesn’t lead to any of the cons.
3. Object-Oriented
Pattern: : <object><method>
Examples:
repositoryOwnerSetEmail
repositoryOwnerSetName
personUpdateAddress
This is similar to how you would call a function/method in many programming languages.
You can see that the 3rd example is the same as the “Namespaces” approach. This happens because person
naturally becomes the namespace. But this happens only on 1st root level entities. You can see that the 2nd example differs between approach #2 and #3, as owner
is nested inside repository
.
4. Shopify’s approach
Pattern: <target><action>
Examples:
repositoryOwnerEmailSet
repositoryOwnerNameSet
personAddressUpdate
This is taken from Shopify’s GraphQL API. You can check one of the leads of Shopify’s API Team explaining some of their rules designing their API in this video.
Honorable mention
This is another option but it’s not a convention but a different design practice. It’s based on this Oleg Ilyenko’s article.
There’s 1 mutation per aggregate root update and inside that input object, each action as “inner” mutation.
type Mutation {
updateDiscountCode(
id: String!,
version: Long!,
actions: [DiscountCodeUpdateAction!]!): DiscountCode
}
input DiscountCodeUpdateAction {
setName: SetDiscountCodeName
setDescription: SetDiscountCodeDescription
setCartPredicate: SetDiscountCodeCartPredicate
setCustomType: SetDiscountCodeCustomType
setCustomField: SetDiscountCodeCustomField
# ...
}
With this you get less first-class mutations and you’re able to send many mutations in one. At the first level you would see the Create/Update/Delete mutations where update has all the specific “nested mutations”.
I encourage you to read the article to fully understand Oleg’s idea and analyze if it makes sense for you.
What about Subscriptions?
I haven’t seen subscriptions in the public APIs, maybe they are hidden, part of the applications’ features. But I would go with either of these two approaches:
1. Noun
Here you subscribe to an event in its noun form, like DeploymentProgress
.
This event can be a union
of multiple events like DeploymentStart
/DeploymentProgress
/DeploymentComplete
for example. If not you will end up having one big type with all optional fields. Or go to the 2nd approach.
2. Past Event
Here you subscribe to an event in its past form, like DeploymentProgressed
or DeploymentCompleted
(the types in the previous approach union).
Thiss way is more granular and you don’t have to resolve what type is coming back from the server, but if these subscriptions are talking about the same deployment
you need to “coordinate” all 3 subscriptions in some way.
Regarding input/payload types, I would follow the same conventions as mutations.
Useful links
- The Apollo Team published a guide on how to design and implement GraphQL, called Principled GraphQL
- GraphQL @ Scale: by Marc-André Giroux from Github
- Article also from Marc-Andre about “Anemic Mutations”, and follow up
- Apollo guidelines for designing mutations
- GraphQL spec
Hope it’s helpful, share if it is! Cheers!