加载一个
类似于 DataLoader 的 load 方法,使用给定的回调函数从您的业务逻辑层读取单个结果。要加载列表,请参阅 loadMany
。
DataLoader 的增强功能
由于 Grafast 中的规划系统,loadOne
可以提供 DataLoader 中无法实现的功能。
属性和参数跟踪
loadOne
步骤(技术上是 LoadedRecordStep
)会跟踪通过 .get(attrName)
访问的属性名称以及通过 .setParam(key, value)
设置的任何参数。此信息将传递到您的回调函数,以便您可以对后端业务逻辑进行更优化的调用,只检索您需要的数据。
输入/输出等效性
如果您(可选)将 ioEquivalence
参数传递给 loadOne
(第二个参数),那么您可以使用它来指示输出上的哪个字段与输入等效。这使得优化成为可能,其中链式获取可以在并行执行,如果子节点仅依赖于与输入等效的输出。希望一个例子可以使这一点更清楚...
假设您正在加载用户及其组织
{
currentUser {
id
name
friends {
id
name
}
}
}
您可能拥有以下计划解析器
const plans = {
Query: {
currentUser() {
const $currentUserId = context().get("userId");
return loadOne($currentUserId, batchGetUserById);
},
},
User: {
friends($user) {
const $userId = $user.get("id");
return loadMany($userId, batchGetFriendsByUserId);
},
},
};
在当前状态下,系统不知道 $user.get("id")
等效于 context().get("userId")
,因此这将导致链式获取
但是,我们可以指示 loadOne
步骤的 id
属性($user.get("id")
)的输出等效于其输入(context().get("userId")
)
const plans = {
Query: {
currentUser() {
const $currentUserId = context().get("userId");
- return loadOne($currentUserId, batchGetUserById);
+ return loadOne($currentUserId, 'id', batchGetUserById);
},
},
User: {
friends($user) {
const $userId = $user.get("id");
return loadMany($userId, batchGetFriendsByUserId);
},
},
};
现在访问 $user.get("id")
将等效于 context().get("userId")
- 我们不再需要等待 $user
加载才能获取朋友
用法
基本用法
const $userId = $post.get("author_id");
const $user = loadOne($userId, batchGetUserById);
// OR: const $user = loadOne($userId, 'id', batchGetUserById);
loadOne
接受两个到四个参数。第一个是指定要加载哪些记录的步骤(指定步骤),最后一个是使用这些规范负责加载它们的回调函数。
回调函数使用两个参数调用,第一个是来自指定步骤的值列表,第二个是可能影响记录获取的选项。
为了获得最佳效果,我们强烈建议将回调函数定义在公共位置,以便可以重复使用,而不是内联定义。这将允许 LoadOneStep 优化对该函数的调用。
可选地,倒数第二个参数(3 个参数中的第 2 个或 4 个参数中的第 3 个)可以指示输入/输出等效性 - 这可以是
null
表示没有输入/输出等效性- 一个字符串,表示输出上具有相同名称的属性等效于整个输入计划
- 如果步骤是
list()
(或类似)计划,则是一个数组,其中包含输出上的键列表(或 null 表示没有关系),这些键等效于输入中的相同条目 - 如果步骤是
object()
(或类似)计划,则是一个对象,它将对象的属性映射到输出中的键,这些键等效于输入上的给定条目
const $member = loadOne(
list([$organizationId, $userId]),
["organization_id", "user_id"],
batchGetMemberByOrganizationIdAndUserId,
);
// - batchGetMemberByOrganizationIdAndUserId will be called with a list of
// 2-tuples, the first value in each tuple being the organizationId and the
// second the userId.
// - Due to the io equivalence (2nd argument):
// - `$member.get("organization_id")` will return `$organizationId` directly
// - `$member.get("user_id")` will return `$userId` directly
const $member = loadOne(
object({ oid: $organizationId, uid: $userId }),
{ oid: "organization_id", uid: "user_id" },
batchGetMemberByOrganizationIdAndUserId,
);
// - batchGetMemberByOrganizationIdAndUserId will be called with a list of
// objects; each object will have the key `oid` set to an organization id,
// and the key `uid` set to the user ID.
// - Due to the io equivalence (2nd argument):
// - `$member.get("organization_id")` will return the step used for `oid`
// (i.e. `$organizationId`) directly
// - Similarly `$member.get("user_id")` will return `$userId` directly
回调示例
回调函数的一个示例可能是
async function batchGetUserById(ids, { attributes }) {
// Your business logic would be called here; e.g. this might be the same
// function that your DataLoaders would call, except we can pass additional
// information to it.
// For example, load from the database
const rows = await db.query(
sql`SELECT id, ${columnsToSql(attributes)} FROM users WHERE id = ANY($1);`,
[ids],
);
// Ensure you return the same number of results, and in the same order!
return ids.map((id) => rows.find((row) => row.id === id));
}
高级用法
const $userId = $post.get("author_id");
const $dbClient = context().get("dbClient");
const $user = loadOne($userId, $dbClient, "id", batchGetUserFromDbById);
// OR: const $user = loadOne($userId, $dbClient, batchGetUserFromDbById);
除了上面“基本用法”中看到的表单外,您还可以将第二个步骤传递给loadOne
。此第二个步骤必须是一元步骤,这意味着它必须在整个请求中表示一个值(而不是像大多数步骤那样表示一批值)。由于我们知道它将只有一个值,我们可以将其作为单个值传递到回调中,并且我们的回调将能够直接使用它,而无需执行任何手动分组。
此一元依赖项对于固定值(例如,来自 GraphQL 字段参数的值)和 GraphQL 上下文上的值(例如,对各种 API 和其他数据源的客户端)很有用。
示例回调(高级)
回调函数的一个示例可能是
async function batchGetUserFromDbById(ids, { attributes, unary }) {
const dbClient = unary;
const rows = await dbClient.query(
sql`SELECT id, ${columnsToSql(attributes)} FROM users WHERE id = ANY($1);`,
[ids],
);
return ids.map((id) => rows.find((row) => row.id === id));
}
多个步骤
如果您需要将多个步骤的值传递到回调中,可以使用list()
或 object()
步骤。
const $isAdmin = $user.get("admin");
const $stripeId = $customer.get("stripe_id");
const $last4 = loadOne(list([$isAdmin, $stripeId]), getLast4FromStripeIfAdmin);
然后,getLast4FromStripeIfAdmin
回调的第一个参数将是一个包含来自这些计划的所有值元组的数组:ReadonlyArray<readonly [isAdmin: boolean, stripeId: string]>
。回调可能看起来像这样
async function getLast4FromStripeIfAdmin(tuples) {
const stripeIds = uniq(
tuples
.filter(([isAdmin, stripeId]) => isAdmin)
.map(([isAdmin, stripeId]) => stripeId),
);
const last4s = await getLast4FromStripeIds(stripeIds);
return tuples.map(([isAdmin, stripeId]) => {
if (!isAdmin) return null;
const index = stripeIds.indexOf(stripeId);
return last4s[index];
});
}
此技术也可以在高级用法中与一元步骤一起使用。