使用带有来自ArangoDB

时间:2018-06-05 05:39:51

标签: javascript graphql arangodb apollo-server arangojs

我正在ArangoDB上实现GraphQL API(带有arangojs),我想知道如何为这个非常基本的用例最好地实现dataloader(或类似)。

我有2个解析器,下面显示了DB查询(这两个工作),第一个提取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)),
    }

}

现在,如果我想以记录作为嵌套数据获取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以使用批量请求/缓存?我可以以某种方式在fetchRecords(id)内运行fetchPerson(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
}
`

2 个答案:

答案 0 :(得分:0)

因此,dataloader的真正好处是它阻止你进行n + 1次查询。例如,如果在您的架构中,Person有一个字段记录,然后您要求前10个人的10个记录。在一个天真的gql架构中,会导致11个请求被触发:前10个人有1个,然后是每个记录有1个。

通过实施dataloader,您可以将其减少到两个请求:一个用于前10个人,然后一个用于前十个人的所有记录。

使用上面的模式,似乎您无法以任何方式从dataloader中受益,因为不可能有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 docs的修改示例:

// 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;
  },
}

答案 1 :(得分:0)

我想我对dataloader的功能感到困惑。服务嵌套数据确实是我的绊脚石。

这是缺少的代码。 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提供。

相关问题