在 GraphQL 中解析相关数据的优雅而高效的方法
Elegant and efficient way to resolve related data in GraphQL
什么是 resolve
GraphQL 中数据的最佳方式
这里我有一个SeekerType
和JobType
,JobsType
嵌套在SeekerType
一个 Seeker 可以申请很多职位。查询求职者时,可以只查询求职者的数据,也可以查询嵌套的JobType
,也可以获取jobstype数据。
但问题是如果不查询嵌套 JobType
他不会获得 Jobs
数据,但我的 Seeker
resolver
在 viewerType
中也会获取该数据。
因此,在向求职者查询提供数据时,我该如何处理,要么他只想要求职者的详细信息,要么可能也想要工作的详细信息。
我应该使用每个 nestedType 的 resolver
并获取父对象,并使用父对象的字段获取相关数据吗???
The code below is just for illustration and clarification, the question is about the best way to resolve data
ViewerType.js
const Viewer = new GraphQLObjectType({
name: 'Viewer',
fields: () => ({
Seeker: {
type: SeekerConnection,
args: _.assign({
seekerId: { type: GraphQLID },
status: { type: GraphQLString },
shortlisted: { type: GraphQLInt },
}, connectionArgs),
resolve: (obj, args, auth, rootValue) => {
const filterArgs = getFilters(args) || {};
return connectionFromPromisedArray(getSeekers(filterArgs), args)
.then((data) => {
// getSeekers() provides all the data required for SeekerType fields and it's
JobsType fields
data.args = filterArgs;
return data;
}).catch(err => new Error(err));
},
},
}),
});
SeekerType.js
const SeekerType = new GraphQLObjectType({
name: 'SeekerType',
fields: () => ({
id: globalIdField('SeekerType', obj => obj._id),
userId: {
type: GraphQLID,
resolve: obj => obj._id,
},
email: { type: GraphQLString },
password: { type: GraphQLString },
firstName: { type: GraphQLString },
lastName: { type: GraphQLString },
imageLink: { type: GraphQLString },
education: { type: GraphQLString },
address: { type: GraphQLString },
jobs: {
type: new GraphQLList(JobType),
},
}),
interfaces: [nodeInterface],
});
getSeekers()
以嵌套的 graphql 字段格式提供完整数据
jobs
字段数据太
const getSeekers = filterArgs => new Promise((resolve, reject) => {
if (Object.keys(filterArgs).length === 0) {
Seeker.find(filterArgs, { password: 0 }, (err, d) => {
if (err) return reject(err);
return resolve(d);
});
} else {
async.parallel([
(callback) => {
filterArgs._id = filterArgs.seekerId;
delete filterArgs.seekerId;
Seeker.find(filterArgs).lean()
.exec((err, d) => {
if (err) return callback(err);
if (err === null && d === null) return callback(null);
callback(null, d);
});
},
(callback) => {
filterArgs.seekerId = filterArgs._id;
delete filterArgs._id;
Applicant.find(filterArgs).populate('jobId').lean()
.exec((err, resp) => {
if (err) return callback(err);
callback(null, resp);
});
},
], (err, data) => {
const cleanedData = {
userData: data[0],
userJobMap: data[1],
};
const result = _.reduce(cleanedData.userData, (p, c) => {
if (c.isSeeker) {
const job = _.filter(cleanedData.userJobMap,
v => _.isEqual(v.seekerId, c._id));
const arr = [];
_.forEach(job, (i) => {
arr.push(i.jobId);
});
const t = _.assign({}, c, { jobs: arr });
p.push(t);
return p;
}
return reject('Not a Seekr');
}, []);
if (err) reject(err);
resolve(result);
// result have both SeekerType data and nested type
JobType data too.
});
}
});
我不确定我是否完全理解这个问题,但在我看来,您似乎在一个级别上同时加载求职者和工作类型信息。您应该按需加载它们。
在搜索者层级,您只获取搜索者信息,您可以获取与该搜索者相关的任何记录的ID。例如,工作类型 id(如果求职者有很多工作类型)
在工作类型级别,当用作一个求职者的嵌套级别时,您可以使用这些 ID 来获取实际记录。这将使在查询要求时获取作业类型记录 on-demand。
可以使用 dataloader
之类的库来缓存和批处理用于记录提取的 ID
我认为这是一个关于如何防止过度获取相关数据的问题...即如何在查询搜索者时不一定请求职位数据。
这可能有多种优化和安全动机。
注意事项:
如果消费者(例如 Web 应用程序)在您的控制之下,您可以简单地避免在查询 seeker 时请求 jobs 字段。正如您可能知道的那样,这是 graphql 的既定目标之一,即仅 return 通过线路向消费者提供所需的内容,以最大程度地减少网络流量并在一次旅行中完成所有事情。在后端,我想 graphql 引擎足够聪明,如果没有请求,也不会过度获取作业数据。
例如,如果您更担心安全性或超出您控制范围的消费者应用程序无意过度获取,那么您可以通过创建单独的查询并限制对它们的访问来解决这个问题。例如。一个针对 seeker 的查询,另一个针对 seekerWithJobsData 的查询。
另一种需要考虑的技术是 graphql 指令,它提供了一个包含开关,可用于有条件地为特定字段提供服务。在您的场景中使用此技术的一个优点可能是允许一种方便的方式根据单个布尔值的值有条件地显示多个查询中的多个字段,例如JobSearchFlag=假。在这里阅读指令的概述:http://graphql.org/learn/queries/
什么是 resolve
GraphQL 中数据的最佳方式
这里我有一个SeekerType
和JobType
,JobsType
嵌套在SeekerType
一个 Seeker 可以申请很多职位。查询求职者时,可以只查询求职者的数据,也可以查询嵌套的JobType
,也可以获取jobstype数据。
但问题是如果不查询嵌套 JobType
他不会获得 Jobs
数据,但我的 Seeker
resolver
在 viewerType
中也会获取该数据。
因此,在向求职者查询提供数据时,我该如何处理,要么他只想要求职者的详细信息,要么可能也想要工作的详细信息。
我应该使用每个 nestedType 的 resolver
并获取父对象,并使用父对象的字段获取相关数据吗???
The code below is just for illustration and clarification, the question is about the best way to resolve data
ViewerType.js
const Viewer = new GraphQLObjectType({
name: 'Viewer',
fields: () => ({
Seeker: {
type: SeekerConnection,
args: _.assign({
seekerId: { type: GraphQLID },
status: { type: GraphQLString },
shortlisted: { type: GraphQLInt },
}, connectionArgs),
resolve: (obj, args, auth, rootValue) => {
const filterArgs = getFilters(args) || {};
return connectionFromPromisedArray(getSeekers(filterArgs), args)
.then((data) => {
// getSeekers() provides all the data required for SeekerType fields and it's
JobsType fields
data.args = filterArgs;
return data;
}).catch(err => new Error(err));
},
},
}),
});
SeekerType.js
const SeekerType = new GraphQLObjectType({
name: 'SeekerType',
fields: () => ({
id: globalIdField('SeekerType', obj => obj._id),
userId: {
type: GraphQLID,
resolve: obj => obj._id,
},
email: { type: GraphQLString },
password: { type: GraphQLString },
firstName: { type: GraphQLString },
lastName: { type: GraphQLString },
imageLink: { type: GraphQLString },
education: { type: GraphQLString },
address: { type: GraphQLString },
jobs: {
type: new GraphQLList(JobType),
},
}),
interfaces: [nodeInterface],
});
getSeekers()
以嵌套的 graphql 字段格式提供完整数据
jobs
字段数据太
const getSeekers = filterArgs => new Promise((resolve, reject) => {
if (Object.keys(filterArgs).length === 0) {
Seeker.find(filterArgs, { password: 0 }, (err, d) => {
if (err) return reject(err);
return resolve(d);
});
} else {
async.parallel([
(callback) => {
filterArgs._id = filterArgs.seekerId;
delete filterArgs.seekerId;
Seeker.find(filterArgs).lean()
.exec((err, d) => {
if (err) return callback(err);
if (err === null && d === null) return callback(null);
callback(null, d);
});
},
(callback) => {
filterArgs.seekerId = filterArgs._id;
delete filterArgs._id;
Applicant.find(filterArgs).populate('jobId').lean()
.exec((err, resp) => {
if (err) return callback(err);
callback(null, resp);
});
},
], (err, data) => {
const cleanedData = {
userData: data[0],
userJobMap: data[1],
};
const result = _.reduce(cleanedData.userData, (p, c) => {
if (c.isSeeker) {
const job = _.filter(cleanedData.userJobMap,
v => _.isEqual(v.seekerId, c._id));
const arr = [];
_.forEach(job, (i) => {
arr.push(i.jobId);
});
const t = _.assign({}, c, { jobs: arr });
p.push(t);
return p;
}
return reject('Not a Seekr');
}, []);
if (err) reject(err);
resolve(result);
// result have both SeekerType data and nested type
JobType data too.
});
}
});
我不确定我是否完全理解这个问题,但在我看来,您似乎在一个级别上同时加载求职者和工作类型信息。您应该按需加载它们。
在搜索者层级,您只获取搜索者信息,您可以获取与该搜索者相关的任何记录的ID。例如,工作类型 id(如果求职者有很多工作类型)
在工作类型级别,当用作一个求职者的嵌套级别时,您可以使用这些 ID 来获取实际记录。这将使在查询要求时获取作业类型记录 on-demand。
可以使用 dataloader
之类的库来缓存和批处理用于记录提取的 ID我认为这是一个关于如何防止过度获取相关数据的问题...即如何在查询搜索者时不一定请求职位数据。
这可能有多种优化和安全动机。
注意事项:
如果消费者(例如 Web 应用程序)在您的控制之下,您可以简单地避免在查询 seeker 时请求 jobs 字段。正如您可能知道的那样,这是 graphql 的既定目标之一,即仅 return 通过线路向消费者提供所需的内容,以最大程度地减少网络流量并在一次旅行中完成所有事情。在后端,我想 graphql 引擎足够聪明,如果没有请求,也不会过度获取作业数据。
例如,如果您更担心安全性或超出您控制范围的消费者应用程序无意过度获取,那么您可以通过创建单独的查询并限制对它们的访问来解决这个问题。例如。一个针对 seeker 的查询,另一个针对 seekerWithJobsData 的查询。
另一种需要考虑的技术是 graphql 指令,它提供了一个包含开关,可用于有条件地为特定字段提供服务。在您的场景中使用此技术的一个优点可能是允许一种方便的方式根据单个布尔值的值有条件地显示多个查询中的多个字段,例如JobSearchFlag=假。在这里阅读指令的概述:http://graphql.org/learn/queries/