使用解析器自动生成 DynamoDB createdAt、updatedAt 和版本属性
Automated DynamoDB createdAt, updatedAt, & version attributes using resolver
我是第一次使用 AWS AppSync 并试图了解 DynamoDB 解析器。我的架构包含三个我希望自动设置的元数据属性:createdAt: AWSTimestamp
、updatedAt: AWSTimestamp
和 version: 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”代码部分。
我是第一次使用 AWS AppSync 并试图了解 DynamoDB 解析器。我的架构包含三个我希望自动设置的元数据属性:createdAt: AWSTimestamp
、updatedAt: AWSTimestamp
和 version: 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”代码部分。