I am using graphql in a hobby project that I am writing, mainly to study and try out new technologies and design patterns.
I have read a few tutorials and examples for using graphql, but in every each and one of them I saw coupling of of the graphql resolvers and the data access.
I want to post here my thoughts and listen to what do you think about it.
I have an 脪ffer` type (and model) which looks like this:
import OfferType from '../types/OfferType';
import {
Offer,
}
from '../models';
const offer = {
type: OfferType,
description: 'Query an offer by its ID.',
args: {
id: {
type: new GraphQLNonNull(GraphQLInt),
},
},
resolve(_, {
id,
}) {
return Offer.findById(id);
},
};
My Offer model is a sequelize model which has methods to access the db.
Unit testing this query will be possible only by mocking the environment (sinon sandbox) or actually accessing the db (which is worse).
I would prefer to export the query as a function that accept the db access, so it can be mocked easily later, something like that:
import OfferType from '../types/OfferType';
const offer = (getOfferById) => {
type: OfferType,
description: 'Query an offer by its ID.',
args: {
id: {
type: new GraphQLNonNull(GraphQLInt),
},
},
resolve(_, {
id,
}) {
return getOfferById(id);
},
};
Since I didn't see it in any example, I would rather hear your thoughts first than rushing to implement it.
So what do you guys think?
GraphQL examples generally have direct data access in the resolver for simplicity, but you're on the right track. For production use cases, prefer to avoid defining business logic in your GraphQL layer. Imagine maintaining a REST API and GraphQL API side-by-side; you'll want to have a single source of truth for enforcing business domain invariants. For more information, take look at the repository pattern: https://martinfowler.com/eaaCatalog/repository.html. Also check out our best practices docs: http://graphql.org/learn/thinking-in-graphs/ and http://graphql.org/learn/authorization/.
Closing since I think the links I provided address your question. Feel free to re-open if that's not the case.
@robzhu - what do you think of solutions like RawModel?
https://github.com/xpepermint/rawmodeljs
https://github.com/xpepermint/graphql-example
Scott
hi @itaied246,
GraphQL Engine gives you the option to pass context into it,
this context will be available thru your resolvers as third argument.
so usually, it will look like this:
import OfferType from '../types/OfferType';
const offer = {
type: OfferType,
description: 'Query an offer by its ID.',
args: {
id: {
type: new GraphQLNonNull(GraphQLInt),
},
},
resolve(_, {
id,
}, ctx) {
return ctx.getOfferById(id);
},
};
while on your main function (or the function that serves the graphql), you will have context that will look like this:
import { Offer } from '../models';
const context = {
getOfferById: (offerId) => Offer.findById(id),
}
...
...
...
graphql(scheme, req.body.query, undefined, context);
then ofcourse when you test the scheme, you can provide a mocked context.
Oh thanks that is very elegant.
It provides a good separation between the data layer and the query layer.
@smolinari I think those types of libraries are great if you have the luxury of using them. In a lot of cases, people are adding GraphQL to a system with a legacy REST/RPC API, which means they have some sort of legacy domain layer for integration. But if you're starting from scratch, I personally like libraries that can standardize things like validation.
@DxCx In a large system, how would one avoid having a context with hundreds of functions? Would the context be different depending on what part of the schema you're in?
Most helpful comment
@DxCx In a large system, how would one avoid having a context with hundreds of functions? Would the context be different depending on what part of the schema you're in?