2016-03-02 32 views
9

Làm cách nào để bạn viết các giải pháp truy vấn trong GraphQL hoạt động tốt với cơ sở dữ liệu quan hệ?Cách thành ngữ, biểu diễn để giải quyết các đối tượng liên quan là gì?

Sử dụng lược đồ ví dụ từ this tutorial, giả sử tôi có cơ sở dữ liệu đơn giản với usersstories. Người dùng có thể tác giả nhiều câu chuyện nhưng câu chuyện chỉ có một người dùng làm tác giả của họ (để đơn giản).

Khi truy vấn người dùng, người dùng cũng có thể muốn nhận danh sách tất cả các câu chuyện do người dùng đó tạo. Một định nghĩa có thể là truy vấn GraphQL để xử lý (bị đánh cắp từ hướng dẫn được liên kết ở trên):

const Query = new GraphQLObjectType({ 
    name: 'Query', 
    fields:() => ({ 
    user: { 
     type: User, 
     args: { 
     id: { 
      type: new GraphQLNonNull(GraphQLID) 
     } 
     }, 
     resolve(parent, {id}, {db}) { 
     return db.get(` 
      SELECT * FROM User WHERE id = $id 
      `, {$id: id}); 
     } 
    }, 
    }) 
}); 

const User = new GraphQLObjectType({ 
    name: 'User', 
    fields:() => ({ 
    id: { 
     type: GraphQLID 
    }, 
    name: { 
     type: GraphQLString 
    }, 
    stories: { 
     type: new GraphQLList(Story), 
     resolve(parent, args, {db}) { 
     return db.all(` 
      SELECT * FROM Story WHERE author = $user 
     `, {$user: parent.id}); 
     } 
    } 
    }) 
}); 

Điều này sẽ hoạt động như mong đợi; nếu tôi truy vấn một người dùng cụ thể, tôi cũng sẽ có thể nhận được câu chuyện của người dùng đó nếu cần. Tuy nhiên, điều này không thực hiện lý tưởng. Nó đòi hỏi hai chuyến đi đến cơ sở dữ liệu, khi một truy vấn đơn lẻ với JOIN sẽ có đủ điều kiện. Vấn đề được khuếch đại nếu tôi truy vấn nhiều người dùng - mỗi người dùng bổ sung sẽ dẫn đến truy vấn cơ sở dữ liệu bổ sung. Vấn đề trở nên tồi tệ hơn theo cấp số nhân sâu hơn tôi đi qua các mối quan hệ đối tượng của tôi.

Sự cố này đã được giải quyết chưa? Có cách nào để viết một trình phân giải truy vấn không dẫn đến các truy vấn SQL không hiệu quả được tạo ra không?

Trả lời

8

Có hai cách tiếp cận cho loại vấn đề này.

Một cách tiếp cận, được sử dụng bởi Facebook, là để enqueue yêu cầu xảy ra trong một đánh dấu và kết hợp chúng với nhau trước khi gửi. Bằng cách này thay vì thực hiện yêu cầu cho từng người dùng, bạn có thể thực hiện một yêu cầu để truy xuất thông tin về một số người dùng. Dan Schafer đã viết một số good comment explaining this approach. Facebook phát hành Dataloader, là một ví dụ thực hiện kỹ thuật này.

// Pass this to graphql-js context 
const storyLoader = new DataLoader((authorIds) => { 
    return db.all(
    `SELECT * FROM Story WHERE author IN (${authorIds.join(',')})` 
).then((rows) => { 
    // Order rows so they match orde of authorIds 
    const result = {}; 
    for (const row of rows) { 
     const existing = result[row.author] || []; 
     existing.push(row); 
     result[row.author] = existing; 
    } 
    const array = []; 
    for (const author of authorIds) { 
     array.push(result[author] || []); 
    } 
    return array; 
    }); 
}); 

// Then use dataloader in your type 
const User = new GraphQLObjectType({ 
    name: 'User', 
    fields:() => ({ 
    id: { 
     type: GraphQLID 
    }, 
    name: { 
     type: GraphQLString 
    }, 
    stories: { 
     type: new GraphQLList(Story), 
     resolve(parent, args, {rootValue: {storyLoader}}) { 
     return storyLoader.load(parent.id); 
     } 
    } 
    }) 
}); 

Trong khi điều này không giải quyết được với SQL hiệu quả, nó vẫn có thể đủ tốt cho nhiều trường hợp sử dụng và làm cho nội dung chạy nhanh hơn. Nó cũng là một cách tiếp cận tốt cho các cơ sở dữ liệu phi quan hệ không cho phép JOIN.

Cách tiếp cận khác là sử dụng thông tin về các trường được yêu cầu trong hàm phân giải để sử dụng JOIN khi có liên quan. Giải quyết ngữ cảnh có trường fieldASTs đã phân tích cú pháp AST của phần truy vấn được giải quyết hiện tại. Bằng cách xem xét các trẻ em của AST đó (selectionSet), chúng ta có thể dự đoán liệu chúng ta có cần tham gia hay không. Một ví dụ rất đơn giản và clunky:

const User = new GraphQLObjectType({ 
    name: 'User', 
    fields:() => ({ 
    id: { 
     type: GraphQLID 
    }, 
    name: { 
     type: GraphQLString 
    }, 
    stories: { 
     type: new GraphQLList(Story), 
     resolve(parent, args, {rootValue: {storyLoader}}) { 
     // if stories were pre-fetched use that 
     if (parent.stories) { 
      return parent.stories; 
     } else { 
      // otherwise request them normally 
      return db.all(` 
      SELECT * FROM Story WHERE author = $user 
     `, {$user: parent.id}); 
     } 
     } 
    } 
    }) 
}); 

const Query = new GraphQLObjectType({ 
    name: 'Query', 
    fields:() => ({ 
    user: { 
     type: User, 
     args: { 
     id: { 
      type: new GraphQLNonNull(GraphQLID) 
     } 
     }, 
     resolve(parent, {id}, {rootValue: {db}, fieldASTs}) { 
     // find names of all child fields 
     const childFields = fieldASTs[0].selectionSet.selections.map(
      (set) => set.name.value 
     ); 
     if (childFields.includes('stories')) { 
      // use join to optimize 
      return db.all(` 
      SELECT * FROM User INNER JOIN Story ON User.id = Story.author WHERE User.id = $id 
      `, {$id: id}).then((rows) => { 
      if (rows.length > 0) { 
       return { 
       id: rows[0].author, 
       name: rows[0].name, 
       stories: rows 
       }; 
      } else { 
       return db.get(` 
       SELECT * FROM User WHERE id = $id 
       `, {$id: id} 
      ); 
      } 
      }); 
     } else { 
      return db.get(` 
      SELECT * FROM User WHERE id = $id 
      `, {$id: id} 
     ); 
     } 
     } 
    }, 
    }) 
}); 

Lưu ý rằng điều này có thể có vấn đề với, ví dụ, các mảnh vỡ. Tuy nhiên, người ta cũng có thể xử lý chúng, nó chỉ là vấn đề kiểm tra việc lựa chọn được thiết lập chi tiết hơn.

Hiện tại có PR trong kho lưu trữ graphql-js, cho phép viết logic phức tạp hơn để tối ưu hóa truy vấn, bằng cách cung cấp 'kế hoạch giải quyết' trong ngữ cảnh.

+0

Ah, tôi nhận thấy thông số 'fieldASTs' trước đây nhưng bây giờ nó có ý nghĩa hơn nhiều khi tôi thấy trường hợp sử dụng cụ thể. Cảm ơn! – ean5533

Các vấn đề liên quan