循环引用 AWS appsync

Circular refrence AWS appsync

我想知道是否可以在 AWS Appsync 中进行循环引用?我已经搜索了很多但找不到它。像这样:

type Post {
    title: String!
    content: String!
    user: User!
}

type Query {
    allPosts: [Post!]
    singlePost(id: String!): Post!
}

type User {
    name: String!
    posts: [Post!]
}

编辑(使用此处描述的逻辑尝试了 lambda https://youtu.be/bgq7FRSPDpI?list=PL55RiY5tL51rG1x02Yyj93iypUuHYXcB_&t=526

这是 allPosts 的 lambda 解析器(将调用处理函数):

import * as sdk from "aws-sdk";

declare var process: {
  env: {
    TABLE_NAME: string;
  };
};

interface Event {
  info: {
    fieldName: string;
    parentTypeName: string;
    variables: Record<string, any>;
  };
}

const client = new sdk.DynamoDB.DocumentClient();

const getUser = (user_id: string): Record<string, any> | null => {
  return client
    .query({
      TableName: process.env.TABLE_NAME,
      KeyConditionExpression: "PK = :pk AND SK = :sk",
      ExpressionAttributeValues: {
        ":pk": user_id,
        ":sk": "profile",
      },
    })
    .promise()
    .then(
      (data) =>
        data.Items?.map((item) => ({
          ...item.data,
          posts: getPost.bind(null, item.PK),
        }))[0]
    )
    .catch((err) => {
      console.log(err);
      return null;
    });
};

const getPost = (user_id: string): Record<string, any> | null => {
  return client
    .query({
      TableName: process.env.TABLE_NAME,
      KeyConditionExpression: "SK = :sk AND pk = :pk",
      ExpressionAttributeValues: {
        ":pk": user_id,
        ":sk": "profile",
      },
    })
    .promise()
    .then((data) =>
      data.Items?.map((item) => ({
        ...item.data,
        user: getUser.bind(null, item.PK),
      }))
    )
    .catch((err) => {
      console.log(err);
      return null;
    });
};

export const handler = async (event: Event) => {
  if (event.info.fieldName === "allPosts") {
    const data = await client
      .query({
        TableName: process.env.TABLE_NAME,
        KeyConditionExpression: "#t = :sk",
        IndexName: "GSI",
        ProjectionExpression: "#d, PK",
        ExpressionAttributeNames: {
          "#t": "type",
          "#d": "data",
        },
        ExpressionAttributeValues: {
          ":sk": "post",
        },
      })
      .promise();
    const result = data.Items?.map((item) => ({
      ...item.data,
      user: getUser.bind(null, item.PK),
    }));
    console.log(data, result);
    return result;
  }
  return;
  //   else if (event.fieldName === "singlePost") {

  //   }
};

用户字段具有此视频中的功能限制:https://youtu.be/bgq7FRSPDpI?list=PL55RiY5tL51rG1x02Yyj93iypUuHYXcB_&t=526

但是 lambda 响应没有返回有界函数。

[
  {
    "title": "post by user_123",
    "content": "\n\nNew to this community. I need some help in designing the Amazon Dynamo DB table for my personal projects.\n\nOverview, this is a simple photo gallery application with following attributes.\n\nUserID\nPostID\nList item\nS3URL\nCaption\nLikes\nReports\nUploadTime\nI wish to perform the following queries:\n\nFor a given user, fetch 'N' most recent posts\nFor a given user, fetch 'N' most liked posts\nGive 'N' most recent posts (Newsfeed)\nGive 'N' most liked posts (Newsfeed)\nMy solution:"
  },
  {
    "title": "another post by user_123",
    "content": "\n\nNew to this community. I need some help in designing the Amazon Dynamo DB table for my personal projects.\n\nOverview, this is a simple photo gallery application with following attributes.\n\nUserID\nPostID\nList item\nS3URL\nCaption\nLikes\nReports\nUploadTime\nI wish to perform the following queries:\n\nFor a given user, fetch 'N' most recent posts\nFor a given user, fetch 'N' most liked posts\nGive 'N' most recent posts (Newsfeed)\nGive 'N' most liked posts (Newsfeed)\nMy solution:"
  }
]

但我可以在日志中看到有界函数:

[
  {
    title: 'post by user_123',
    content: '\n' +
      '\n' +
      'New to this community. I need some help in designing the Amazon Dynamo DB table for my personal projects.\n' +
      '\n' +
      'Overview, this is a simple photo gallery application with following attributes.\n' +
      '\n' +
      'UserID\n' +
      'PostID\n' +
      'List item\n' +
      'S3URL\n' +
      'Caption\n' +
      'Likes\n' +
      'Reports\n' +
      'UploadTime\n' +
      'I wish to perform the following queries:\n' +
      '\n' +
      "For a given user, fetch 'N' most recent posts\n" +
      "For a given user, fetch 'N' most liked posts\n" +
      "Give 'N' most recent posts (Newsfeed)\n" +
      "Give 'N' most liked posts (Newsfeed)\n" +
      'My solution:',
    user: [Function: bound getUser]
  },
  {
    title: 'another post by user_123',
    content: '\n' +
      '\n' +
      'New to this community. I need some help in designing the Amazon Dynamo DB table for my personal projects.\n' +
      '\n' +
      'Overview, this is a simple photo gallery application with following attributes.\n' +
      '\n' +
      'UserID\n' +
      'PostID\n' +
      'List item\n' +
      'S3URL\n' +
      'Caption\n' +
      'Likes\n' +
      'Reports\n' +
      'UploadTime\n' +
      'I wish to perform the following queries:\n' +
      '\n' +
      "For a given user, fetch 'N' most recent posts\n" +
      "For a given user, fetch 'N' most liked posts\n" +
      "Give 'N' most recent posts (Newsfeed)\n" +
      "Give 'N' most liked posts (Newsfeed)\n" +
      'My solution:',
    user: [Function: bound getUser]
  }
]

TL;DR 是的,appsync 可以轻松处理嵌套或“循环”查询。关键的见解是,解析 user 字段后面的 User 类型不是 allPosts 处理程序的工作。相反,appsync 将调用 lambda 解析器 第二次 以获取 user 字段的 User。我们需要在 lambda 中添加分支逻辑来处理第二次调用,其中 event.info.fieldName === "user".

// a switch statement inside your lambda function handler "routes" the request

switch (event.parentTypeName) {
  case "Query":
    switch (event.fieldName) {
      case "allPosts":
        // depends on your schema
        const userID = event.arguments?.["id"]
        // handle query, return [Post!], as per your schema
      case "singlePost"
        const postID = event.arguments?["id"]
        // ditto, return Post!, as per your schema
    }
  case "Post":
    switch (event.fieldName) {
      case "user":
       // event.source is a Post, details depend on your actual schema
       const userID = event.source?.["userID"]
       // make dynamo call to get the User and return a User, type that your graphql schema is expecting
    }
  case "User":
    switch (event.fieldName) {
      case "posts":
         // event.source is a User, details depend on your actual schema
        const userID = event.source?.["id"]
        // fetch posts from dynamo, return [Post!], the type your graphql schema is expecting
    }
  default:
    throw new Error("unhandled parent type")
}

Context:这个答案和问题一样,假设我们的数据源是 direct lambda resolver, meaning our function receives as an arg the full context object 并且我们不使用 VTL 模板。它还假定一般默认选择具有单个 lambda 解析器和巨大的 switch 语句来处理各种传入请求。

可解析字段由 appsync 单独解析。可解析字段具有解析器数据源。在我们的例子中,它是一个 lambda 函数。对于 appsync 遇到的每个可解析字段,appsync 将单独调用解析器。 (顺便说一句,我的猜测是你 已经 配置了 User 数据源。这将解开为什么 allPosts 不是 returning 的谜团用户结果。解析用户不是它的工作,你的 lambda 当前没有处理 event.info.fieldName of user).

设置解析器 aws 为我们提供了几种将解析器分配给字段的方法,包括在控制台(模式选项卡、附加按钮)和 cdk (add a LambdaDataSource to a ObjectType 中。

我们如何获得 user ID? Appsync 为我们提供了 event 参数 user PK/SK 信息我们的 dynamodb 调用。 event 为我们提供了很多信息,包括 fieldNameparentTypeName 以及至关重要的 source 键以及父字段的值。 console.log event 发现您需要处理的内容。

额外学分

问:appsync 会为以下查询调用我们的 lambda 多少次?

query allPosts(limit: 10) {
    title
    content
    user {
        name
    }
}

A: 11 次。 allPosts 为 1 倍,user 为 10 倍。

现有技术 参见 and SO post 以获得类似的答案。

不要在家里尝试这个 理论上可以删除 User 类型的解析器,从而交给 allPosts (和其他查询)对 return 用户的责任。由于查询 return 值的潜在深层嵌套,零增益相当复杂。