代码之家  ›  专栏  ›  技术社区  ›  rm.rf.etc

将dataloader用于具有来自arangodb的嵌套数据的解析器

  •  2
  • rm.rf.etc  · 技术社区  · 7 年前

    我在ArangoDB上实现一个GraphQLAPI(使用ArangoJS),我想知道如何最好地实现 dataloader (或类似)对于这个非常基本的用例。

    我有两个解析器,其中包含如下所示的数据库查询(这两个工作),第一个获取人员,第二个获取与给定人员(一对多)关联的记录对象列表。使用arangob的边缘集合进行关联。

    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) 但只有当 取款人(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 回复  |  直到 7 年前
        1
  •  0
  •   Matthew Herbst    7 年前

    因此,dataloader的真正好处是它阻止您执行n+1查询。也就是说,如果在您的模式中,person有一个字段记录,那么您要求前10个人的10个记录。在一个幼稚的gql模式中,这将导致11个请求被触发:前10个人有1个请求,然后每个记录有一个请求。

    在dataloader的实现中,您可以将其减少到两个请求:一个用于前10个人,一个用于前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 方便分享。阿波罗文档的修改示例:

    // 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;
      },
    }
    
        2
  •  0
  •   rm.rf.etc    7 年前

    我想我对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]!
    }
    

    似乎这些特征是由阿波罗提供的 graphql-tools .

    推荐文章