将数据加载器用于具有来自 ArangoDB 的嵌套数据的解析器
Using dataloader for resolvers with nested data from ArangoDB
我正在通过 ArangoDB(使用 arangojs)实现 GraphQL API,我想知道如何针对这个非常基本的用例最好地实现 dataloader
(或类似的)。
我有 2 个具有如下所示的数据库查询的解析器(这两个都有效),第一个获取 Persons,第二个获取与给定 Person 关联的 Record 对象列表(一对多)。该关联是使用 ArangoDB 的边缘集合建立的。
import { Database, aql } from 'arangojs'
import pick from 'lodash/pick'
const db = new Database('http://127.0.0.1:8529')
db.useBasicAuth('root', '')
db.useDatabase('_system')
// id is the auto-generated userId, which `_key` in Arango
const fetchPerson = id=> async (resolve, reject)=> {
try {
const cursor = await db.query(aql`RETURN DOCUMENT("PersonTable", ${String(id)})`)
// Unwrap the results from the cursor object
const result = await cursor.next()
return resolve( pick(result, ['_key', 'firstName', 'lastName']) )
} catch (err) {
return reject( err )
}
}
// id is the auto-generated userId (`_key` in Arango) who is associated with the records via the Person_HasMany_Records edge collection
const fetchRecords = id=> async (resolve, reject)=> {
try {
const edgeCollection = await db.collection('Person_HasMany_Records')
// Query simply says: `get all connected nodes 1 step outward from origin node, in edgeCollection`
const cursor = await db.query(aql`
FOR record IN 1..1
OUTBOUND DOCUMENT("PersonTable", ${String(id)})
${edgeCollection}
RETURN record`)
return resolve( cursor.map(each=>
pick(each, ['_key', 'intro', 'title', 'misc']))
)
} catch (err) {
return reject( err )
}
}
export default {
Query: {
getPerson: (_, { id })=> new Promise(fetchPerson(id)),
getRecords: (_, { ownerId })=> new Promise(fetchRecords(ownerId)),
}
}
现在,如果我想获取带有 Records 的 Person 数据作为嵌套数据,在单个请求中,查询将是这样的:
aql`
LET person = DOCUMENT("PersonTable", ${String(id)})
LET records = (
FOR record IN 1..1
OUTBOUND person
${edgeCollection}
RETURN record
)
RETURN MERGE(person, { records: records })`
那么我应该如何更新我的 API 以使用批处理请求/缓存?我可以在 fetchPerson(id)
中以某种方式 运行 fetchRecords(id)
但仅当 fetchPerson(id)
被调用时包含 records
属性 吗?
安装文件在这里,注意我使用的是 graphql-tools
,因为我从某个地方的教程中获取了它。
import http from 'http'
import db from './database'
import schema from './schema'
import resolvers from './resolvers'
import express from 'express'
import bodyParser from 'body-parser'
import { graphqlExpress, graphiqlExpress } from 'apollo-server-express'
import { makeExecutableSchema } from 'graphql-tools'
const app = express()
// bodyParser is needed just for POST.
app.use('/graphql', bodyParser.json(), graphqlExpress({
schema: makeExecutableSchema({ typeDefs: schema, resolvers })
}))
app.get('/graphiql', graphiqlExpress({ endpointURL: '/graphql' })) // if you want GraphiQL enabled
app.listen(3000)
这是架构。
export default `
type Person {
_key: String!
firstName: String!
lastName: String!
}
type Records {
_key: String!
intro: String!
title: String!
misc: String!
}
type Query {
getPerson(id: Int!): Person
getRecords(ownerId: Int!): [Record]!
}
type Schema {
query: Query
}
`
因此,dataloader 的真正好处在于它可以阻止您进行 n+1 次查询。意思是例如,如果在您的模式中,Person 有一个字段记录,然后您要求前 10 个人的 10 条记录。在一个简单的 gql 模式中,这将导致 11 个请求被触发:前 10 个人 1 个,然后他们的每条记录一个。
实施数据加载器后,您将其减少为两个请求:一个针对前 10 个人,然后一个针对前 10 个人的所有记录。
使用上面的架构,您似乎无法从数据加载器中受益,因为不可能进行 n+1 次查询。如果您在单个请求中对同一个人或记录发出多个请求,您可能获得的唯一好处是缓存(同样,根据您的架构设计,除非您使用批量查询,否则这是不可能的)。
假设您想要缓存。然后你可以这样做:
// loaders.js
// The callback functions take a list of keys and return a list of values to
// hydrate those keys, in order, with `null` for any value that cannot be hydrated
export default {
personLoader: new DataLoader(loadBatchedPersons),
personRecordsLoader: new DataLoader(loadBatchedPersonRecords),
};
然后您想要将加载程序附加到您的 context 以便于共享。来自 Apollo 文档的修改示例:
// app.js
import loaders from './loaders';
app.use(
'/graphql',
bodyParser.json(),
graphqlExpress(req => {
return {
schema: myGraphQLSchema,
context: {
loaders,
},
};
}),
);
然后,您可以在解析器的上下文中使用它们:
// ViewerType.js:
// Some parent type, such as `viewer` often
{
person: {
type: PersonType,
resolve: async (viewer, args, context, info) => context.loaders.personLoader,
},
records: {
type: new GraphQLList(RecordType), // This could also be a connection
resolve: async (viewer, args, context, info) => context.loaders.personRecordsLoader;
},
}
我想我对数据加载器的功能感到困惑。提供嵌套数据确实是我的绊脚石。
这是缺少的代码。 resolvers.js 的导出需要 person
属性,
export default {
Person: {
records: (person)=> new Promise(fetchRecords(person._key)),
},
Query: {
getPerson: (_, { id })=> new Promise(fetchPerson(id)),
getRecords: (_, { ownerId })=> new Promise(fetchRecords(ownerId)),
},
}
模式中的 Person 类型需要 records
属性。
type Person {
_key: String!
firstName: String!
lastName: String!
records: [Records]!
}
这些功能似乎是由 Apollo graphql-tools
提供的。
我正在通过 ArangoDB(使用 arangojs)实现 GraphQL API,我想知道如何针对这个非常基本的用例最好地实现 dataloader
(或类似的)。
我有 2 个具有如下所示的数据库查询的解析器(这两个都有效),第一个获取 Persons,第二个获取与给定 Person 关联的 Record 对象列表(一对多)。该关联是使用 ArangoDB 的边缘集合建立的。
import { Database, aql } from 'arangojs'
import pick from 'lodash/pick'
const db = new Database('http://127.0.0.1:8529')
db.useBasicAuth('root', '')
db.useDatabase('_system')
// id is the auto-generated userId, which `_key` in Arango
const fetchPerson = id=> async (resolve, reject)=> {
try {
const cursor = await db.query(aql`RETURN DOCUMENT("PersonTable", ${String(id)})`)
// Unwrap the results from the cursor object
const result = await cursor.next()
return resolve( pick(result, ['_key', 'firstName', 'lastName']) )
} catch (err) {
return reject( err )
}
}
// id is the auto-generated userId (`_key` in Arango) who is associated with the records via the Person_HasMany_Records edge collection
const fetchRecords = id=> async (resolve, reject)=> {
try {
const edgeCollection = await db.collection('Person_HasMany_Records')
// Query simply says: `get all connected nodes 1 step outward from origin node, in edgeCollection`
const cursor = await db.query(aql`
FOR record IN 1..1
OUTBOUND DOCUMENT("PersonTable", ${String(id)})
${edgeCollection}
RETURN record`)
return resolve( cursor.map(each=>
pick(each, ['_key', 'intro', 'title', 'misc']))
)
} catch (err) {
return reject( err )
}
}
export default {
Query: {
getPerson: (_, { id })=> new Promise(fetchPerson(id)),
getRecords: (_, { ownerId })=> new Promise(fetchRecords(ownerId)),
}
}
现在,如果我想获取带有 Records 的 Person 数据作为嵌套数据,在单个请求中,查询将是这样的:
aql`
LET person = DOCUMENT("PersonTable", ${String(id)})
LET records = (
FOR record IN 1..1
OUTBOUND person
${edgeCollection}
RETURN record
)
RETURN MERGE(person, { records: records })`
那么我应该如何更新我的 API 以使用批处理请求/缓存?我可以在 fetchPerson(id)
中以某种方式 运行 fetchRecords(id)
但仅当 fetchPerson(id)
被调用时包含 records
属性 吗?
安装文件在这里,注意我使用的是 graphql-tools
,因为我从某个地方的教程中获取了它。
import http from 'http'
import db from './database'
import schema from './schema'
import resolvers from './resolvers'
import express from 'express'
import bodyParser from 'body-parser'
import { graphqlExpress, graphiqlExpress } from 'apollo-server-express'
import { makeExecutableSchema } from 'graphql-tools'
const app = express()
// bodyParser is needed just for POST.
app.use('/graphql', bodyParser.json(), graphqlExpress({
schema: makeExecutableSchema({ typeDefs: schema, resolvers })
}))
app.get('/graphiql', graphiqlExpress({ endpointURL: '/graphql' })) // if you want GraphiQL enabled
app.listen(3000)
这是架构。
export default `
type Person {
_key: String!
firstName: String!
lastName: String!
}
type Records {
_key: String!
intro: String!
title: String!
misc: String!
}
type Query {
getPerson(id: Int!): Person
getRecords(ownerId: Int!): [Record]!
}
type Schema {
query: Query
}
`
因此,dataloader 的真正好处在于它可以阻止您进行 n+1 次查询。意思是例如,如果在您的模式中,Person 有一个字段记录,然后您要求前 10 个人的 10 条记录。在一个简单的 gql 模式中,这将导致 11 个请求被触发:前 10 个人 1 个,然后他们的每条记录一个。
实施数据加载器后,您将其减少为两个请求:一个针对前 10 个人,然后一个针对前 10 个人的所有记录。
使用上面的架构,您似乎无法从数据加载器中受益,因为不可能进行 n+1 次查询。如果您在单个请求中对同一个人或记录发出多个请求,您可能获得的唯一好处是缓存(同样,根据您的架构设计,除非您使用批量查询,否则这是不可能的)。
假设您想要缓存。然后你可以这样做:
// loaders.js
// The callback functions take a list of keys and return a list of values to
// hydrate those keys, in order, with `null` for any value that cannot be hydrated
export default {
personLoader: new DataLoader(loadBatchedPersons),
personRecordsLoader: new DataLoader(loadBatchedPersonRecords),
};
然后您想要将加载程序附加到您的 context 以便于共享。来自 Apollo 文档的修改示例:
// app.js
import loaders from './loaders';
app.use(
'/graphql',
bodyParser.json(),
graphqlExpress(req => {
return {
schema: myGraphQLSchema,
context: {
loaders,
},
};
}),
);
然后,您可以在解析器的上下文中使用它们:
// ViewerType.js:
// Some parent type, such as `viewer` often
{
person: {
type: PersonType,
resolve: async (viewer, args, context, info) => context.loaders.personLoader,
},
records: {
type: new GraphQLList(RecordType), // This could also be a connection
resolve: async (viewer, args, context, info) => context.loaders.personRecordsLoader;
},
}
我想我对数据加载器的功能感到困惑。提供嵌套数据确实是我的绊脚石。
这是缺少的代码。 resolvers.js 的导出需要 person
属性,
export default {
Person: {
records: (person)=> new Promise(fetchRecords(person._key)),
},
Query: {
getPerson: (_, { id })=> new Promise(fetchPerson(id)),
getRecords: (_, { ownerId })=> new Promise(fetchRecords(ownerId)),
},
}
模式中的 Person 类型需要 records
属性。
type Person {
_key: String!
firstName: String!
lastName: String!
records: [Records]!
}
这些功能似乎是由 Apollo graphql-tools
提供的。