使用解析器自动生成 DynamoDB createdAt、updatedAt 和版本属性

Automated DynamoDB createdAt, updatedAt, & version attributes using resolver

我是第一次使用 AWS AppSync 并试图了解 DynamoDB 解析器。我的架构包含三个我希望自动设置的元数据属性:createdAt: AWSTimestampupdatedAt: AWSTimestampversion: Integer。我可以在 creatItem 解析器中设置它们的初始值,但我无法理解 updateItem 解析器的工作原理。

作为参考,这一切都在与此类似的 URL 处,在 AWS 上: https://console.aws.amazon.com/appsync/home#/[apiId]/v1/schema/Mutation/updateItem/resolver

这是我现在的代码,经过大量的反复试验:

  ##Update versioning attributes
  #if( !${expNames.isEmpty()} )
    $!{expSet.put("#updatedAt", ":updatedAt")}
    $!{expNames.put("#updatedAt", "updatedAt")}
    $!{expValues.put(":updatedAt", $util.dynamodb.toDynamoDB($util.time.nowEpochMilliSeconds() ))}

    $!{expSet.put("#version", ":version")}
    $!{expNames.put("#version", "version")}
    $!{expValues.put(":version", $util.dynamodb.toNumber(99))}
  #end

这是当前错误:

{
  "data": {
    "updateItem": null
  },
  "errors": [
    {
      "path": [
        "updateItem"
      ],
      "data": null,
      "errorType": "DynamoDB:AmazonDynamoDBException",
      "errorInfo": null,
      "locations": [
        {
          "line": 74,
          "column": 3,
          "sourceName": null
        }
      ],
      "message": "Value provided in ExpressionAttributeNames unused in expressions: keys: {#updatedAt, #version} (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ValidationException; Request ID: YADDAYADDAYADDA)"
    }
  ]
}

您还会注意到我将版本设置为静态值 99 而不是 n+1。这是我接下来要解决的问题,但如果您有任何提示,我很乐意提供。

这是完整的解析器,包括所有 AWS 样板文件:

{
  "version": "2017-02-28",
  "operation": "UpdateItem",
  "key": {
    "id": $util.dynamodb.toDynamoDBJson($ctx.args.input.id),
  },

  ## Set up some space to keep track of things we're updating **
  #set( $expNames  = {} )
  #set( $expValues = {} )
  #set( $expSet = {} )
  #set( $expAdd = {} )
  #set( $expRemove = [] )

  ## Iterate through each argument, skipping keys **
  #foreach( $entry in $util.map.copyAndRemoveAllKeys($ctx.args.input, ["id"]).entrySet() )
    #if( $util.isNull($entry.value) )
      ## If the argument is set to "null", then remove that attribute from the item in DynamoDB **

      #set( $discard = ${expRemove.add("#${entry.key}")} )
      $!{expNames.put("#${entry.key}", "${entry.key}")}
    #else
      ## Otherwise set (or update) the attribute on the item in DynamoDB **

      $!{expSet.put("#${entry.key}", ":${entry.key}")}
      $!{expNames.put("#${entry.key}", "${entry.key}")}
      $!{expValues.put(":${entry.key}", $util.dynamodb.toDynamoDB($entry.value))}
    #end
  #end

  ## Start building the update expression, starting with attributes we're going to SET **
  #set( $expression = "" )
  #if( !${expSet.isEmpty()} )
    #set( $expression = "SET" )
    #foreach( $entry in $expSet.entrySet() )
      #set( $expression = "${expression} ${entry.key} = ${entry.value}" )
      #if ( $foreach.hasNext )
        #set( $expression = "${expression}," )
      #end
    #end
  #end

  ## Continue building the update expression, adding attributes we're going to ADD **
  #if( !${expAdd.isEmpty()} )
    #set( $expression = "${expression} ADD" )
    #foreach( $entry in $expAdd.entrySet() )
      #set( $expression = "${expression} ${entry.key} ${entry.value}" )
      #if ( $foreach.hasNext )
        #set( $expression = "${expression}," )
      #end
    #end
  #end

  ## Continue building the update expression, adding attributes we're going to REMOVE **
  #if( !${expRemove.isEmpty()} )
    #set( $expression = "${expression} REMOVE" )

    #foreach( $entry in $expRemove )
      #set( $expression = "${expression} ${entry}" )
      #if ( $foreach.hasNext )
        #set( $expression = "${expression}," )
      #end
    #end
  #end



  ##Update versioning attributes
  #if( !${expNames.isEmpty()} )
    $!{expSet.put("#updatedAt", ":updatedAt")}
    $!{expNames.put("#updatedAt", "updatedAt")}
    $!{expValues.put(":updatedAt", $util.dynamodb.toDynamoDB($util.time.nowEpochMilliSeconds() ))}

    $!{expSet.put("#version", ":version")}
    $!{expNames.put("#version", "version")}
    $!{expValues.put(":version", $util.dynamodb.toNumber(99))}
  #end

  ## Finally, write the update expression into the document, along with any expressionNames and expressionValues **
  "update": {
    "expression": "${expression}",
    #if( !${expNames.isEmpty()} )
      "expressionNames": $utils.toJson($expNames),
    #end
    #if( !${expValues.isEmpty()} )
      "expressionValues": $utils.toJson($expValues),
    #end
  },

  "condition": {
    "expression": "attribute_exists(#id)",
    "expressionNames": {
      "#id": "id",
    },
  }
}

提前致谢!

编辑:我试图概括上面的 table 名称,但这是请求的实际架构:

input CreateMapInput {
    title: String!
    imageUrl: AWSURL!
    center: AWSJSON
    zoom: Float
    bearing: Float
    imageType: String
    sourceSize: AWSJSON
    cropSize: AWSJSON
    cropAnchor: AWSJSON
    corners: AWSJSON
    country: String
    city: String
    published: Boolean
}

input DeleteMapInput {
    id: ID!
}

type Map {
    id: ID!
    title: String!
    imageUrl: AWSURL!
    center: AWSJSON
    zoom: Float
    bearing: Float
    imageType: String
    sourceSize: AWSJSON
    cropSize: AWSJSON
    cropAnchor: AWSJSON
    corners: AWSJSON
    country: String
    city: String
    published: Boolean
    createdAt: AWSTimestamp
    updatedAt: AWSTimestamp
version: Int
}

type MapConnection {
    items: [Map]
    nextToken: String
}

type Mutation {
    createMap(input: CreateMapInput!): Map
    updateMap(input: UpdateMapInput!): Map
    deleteMap(input: DeleteMapInput!): Map
}

type Query {
    getMap(id: ID!): Map
    listMaps(filter: TableMapFilterInput, limit: Int, nextToken: String): MapConnection
}

type Subscription {
    onCreateMap(
        id: ID,
        title: String,
        imageUrl: AWSURL,
        center: AWSJSON,
        zoom: Float
    ): Map
        @aws_subscribe(mutations: ["createMap"])
    onUpdateMap(
        id: ID,
        title: String,
        imageUrl: AWSURL,
        center: AWSJSON,
        zoom: Float
    ): Map
        @aws_subscribe(mutations: ["updateMap"])
    onDeleteMap(
        id: ID,
        title: String,
        imageUrl: AWSURL,
        center: AWSJSON,
        zoom: Float
    ): Map
        @aws_subscribe(mutations: ["deleteMap"])
}

input TableBooleanFilterInput {
    ne: Boolean
    eq: Boolean
}

input TableFloatFilterInput {
    ne: Float
    eq: Float
    le: Float
    lt: Float
    ge: Float
    gt: Float
    contains: Float
    notContains: Float
    between: [Float]
}

input TableIDFilterInput {
    ne: ID
    eq: ID
    le: ID
    lt: ID
    ge: ID
    gt: ID
    contains: ID
    notContains: ID
    between: [ID]
    beginsWith: ID
}

input TableIntFilterInput {
    ne: Int
    eq: Int
    le: Int
    lt: Int
    ge: Int
    gt: Int
    contains: Int
    notContains: Int
    between: [Int]
}

input TableMapFilterInput {
    id: TableIDFilterInput
    title: TableStringFilterInput
    imageUrl: TableStringFilterInput
    zoom: TableFloatFilterInput
    bearing: TableFloatFilterInput
    imageType: TableStringFilterInput
    country: TableStringFilterInput
    city: TableStringFilterInput
    published: TableBooleanFilterInput
    createdAt: TableIntFilterInput
    updatedAt: TableIntFilterInput
    version: TableIntFilterInput
}

input TableStringFilterInput {
    ne: String
    eq: String
    le: String
    lt: String
    ge: String
    gt: String
    contains: String
    notContains: String
    between: [String]
    beginsWith: String
}

input UpdateMapInput {
    id: ID!
    title: String
    imageUrl: AWSURL
    center: AWSJSON
    zoom: Float
    bearing: Float
    imageType: String
    sourceSize: AWSJSON
    cropSize: AWSJSON
    cropAnchor: AWSJSON
    corners: AWSJSON
    country: String
    city: String
    published: Boolean
}

此外,查询和变量:

mutation updateMap($updatemapinput: UpdateMapInput!) {
  updateMap(input: $updatemapinput) {
    id
    title
    imageUrl
    center
    zoom
    bearing
    imageType
    sourceSize
    cropSize
    cropAnchor
    corners
    country
    city
    published
    createdAt
    updatedAt
    version
  }
}
{
  "updatemapinput": {
    "id": "e1be0d61-9e8d-4f85-b0ed-91a23527f3e7",
    "zoom": 14
  }
}

这里的问题是您没有使用您设置的属性更新表达式变量。

如果您将代码更新为此,它应该可以工作。

##Update versioning attributes
  #if( !${expNames.isEmpty()} )
    #set( $expression = "${expression}, SET updatedAt = $util.time.nowEpochMilliSeconds()" )
    $!{expSet.put("#updatedAt", ":updatedAt")}
    $!{expNames.put("#updatedAt", "updatedAt")}
    $!{expValues.put(":updatedAt", $util.dynamodb.toDynamoDB($util.time.nowEpochMilliSeconds() ))}

    #set( $expression = "${expression}, SET version = $util.dynamodb.toNumber(99)" )
    $!{expSet.put("#version", ":version")}
    $!{expNames.put("#version", "version")}
    $!{expValues.put(":version", $util.dynamodb.toNumber(99))}
  #end

我推荐的另一个选项是在模板开头的输入中插入值,这样,您就不必处理更新表达式变量的问题。前面的可以这样实现

#set($ctx.args.input['updatedAt'] = $util.time.nowEpochMilliSeconds())
#set($ctx.args.input['version'] = 99)

并且您必须删除添加到模板的“##Update versioning attributes”代码部分。