SmokeDynamoDB 是一个库,旨在简化从基于 Swift 的应用程序中使用 DynamoDB 的过程,尤其侧重于多态数据库表(即并非所有行都具有单一 schema 的表)的使用。
SmokeDynamoDB 使用 Swift Package Manager。 要使用该框架,请将以下依赖项添加到您的 Package.swift 文件中:
对于 swift-tools 版本 5.2 及更高版本:
dependencies: [
.package(url: "https://github.com/amzn/smoke-dynamodb", from: "3.0.0-alpha.5")
]
.target(name: ..., dependencies: [
...,
.product(name: "SmokeDynamoDB", package: "smoke-dynamodb"),
]),
对于 swift-tools 版本 5.1 及更早版本:
dependencies: [
.package(url: "https://github.com/amzn/smoke-dynamodb", from: "3.0.0-alpha.5")
]
.target(
name: ...,
dependencies: [..., "SmokeDynamoDB"]),
为了保持整个库命名的一致性,SmokeDynamoDB 将遵循 AWS DynamoDB 文档中观察和标准化的命名方式。
大写:DynamoDB
DynamoDBCompositePrimaryKeyTable
dropAsDynamoDBKeyPrefix
小写:dynamodb
dynamodbKeyWithPrefixedVersion
此包允许使用符合 DynamoDBCompositePrimaryKeyTable
协议的类型对 DynamoDB 表执行操作。 在生产环境中,可以使用 AWSDynamoDBCompositePrimaryKeyTable
执行操作。
对于基本用例,您可以使用 tableName、credentialsProvider 和 awsRegion 初始化一个表。
let table = AWSDynamoDBCompositePrimaryKeyTable(tableName: tableName,
credentialsProvider: credentialsProvider,
awsRegion: awsRegion)
...
try await table.shutdown()
此类的初始化器还可以接受可选参数,例如 Logger
、EventLoop
和 InternalRequestId
来使用。
传递 EventLoop
对于也使用 SwiftNIO 作为服务器的应用程序非常有用,并且希望在与服务器的传入请求相同的 EventLoop
上处理下游服务调用。
为了在实例之间共享客户端配置或底层 http 客户端(例如在基于请求的服务中,您可能希望为每个请求创建一个实例),还可以使用 AWSDynamoDBClientConfiguration
和 AWSDynamoDBTableOperationsClient
类型-
一种选择是创建一次配置(例如在应用程序启动时)-
let config = AWSDynamoDBClientConfiguration(
credentialsProvider: credentialsProvider, region: region)
然后根据需要创建 AWSDynamoDBCompositePrimaryKeyTable
-
let table = AWSDynamoDBCompositePrimaryKeyTable(config: config,
tableName: tableName,
logger: theCurrentLogger,
internalRequestId: theCurrentRequestId,
eventLoop: theCurrentEventLoop)
...
try await table.shutdown()
或者,创建一次操作客户端(例如在应用程序启动时)-
let operationsClient = AWSDynamoDBTableOperationsClient(
tableName: tableName, credentialsProvider: credentialsProvider, region: region)
...
try await operationsClient.shutdown()
然后根据需要创建 AWSDynamoDBCompositePrimaryKeyTable
-
let table = AWSDynamoDBCompositePrimaryKeyTable(operationsClient: operationsClient,
logger: theCurrentLogger,
internalRequestId: theCurrentRequestId,
eventLoop: theCurrentEventLoop)
基于 SmokeFramework(https://github.com/amzn/smoke-framework)的应用程序可以通过在创建表时传递报告上下文来自动实现基于请求的 EventLoop
亲和性-
public func getInvocationContext(invocationReporting: SmokeServerInvocationReporting<SmokeInvocationTraceContext>) -> MyContext {
let awsClientInvocationReporting = invocationReporting.withInvocationTraceContext(traceContext: awsClientInvocationTraceContext)
let table = AWSDynamoDBCompositePrimaryKeyTable(operationsClient: operationsClient,
reporting: awsClientInvocationReporting)
return MyContext(dynamodbTable: dynamodbTable)
}
InMemory*
类型(例如 InMemoryDynamoDBCompositePrimaryKeyTable
)提供了通过使用内存字典来模拟 DynamoDb 表的行为,从而执行表操作的基本验证的能力。 这些类型不会模拟更高级的行为,例如索引。
SimulateConcurrency*
类型提供了一个包装器,围绕着另一个表,并在访问之间模拟对该表的其他写入。 这些类型旨在允许对表并发处理进行单元测试。
使用 Amazon DynamoDB 的可下载版本,您可以在不访问 DynamoDB Web 服务的情况下开发和测试应用程序。 当本地测试需要 DynamoDB 的全部功能时,可以使用此版本。
设置 DynamoDB Local 的说明位于 此处。
然后,您可以使用以下代码调用 DynamoDB Local。
import SmokeDynamoDB
import SmokeAWSCore
import SmokeAWSHttp
import Logging
let credentials = StaticCredentials(accessKeyId: "accessKeyId",
secretAccessKey: "secretAccessKey",
sessionToken: nil)
let generator = AWSDynamoDBCompositePrimaryKeyTableGenerator(
credentialsProvider: credentials,
region: .us_west_2,
endpointHostName: "127.0.0.1",
endpointPort: 8000,
tableName: "MyTableName")
defer {
try? generator.close()
}
let table = generator.with(logger: Logger(label: "test.logger"))
...
DynamoDB Local 需要发送凭据,但这些凭据不需要对应于之前设置的任何内容。
可以使用以下方法将项目插入到 DynamoDB 表中-
struct PayloadType: Codable, Equatable {
let firstly: String
let secondly: String
}
let key = StandardCompositePrimaryKey(partitionKey: "partitionId",
sortKey: "sortId")
let payload = PayloadType(firstly: "firstly", secondly: "secondly")
let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload)
try table.insertItem(databaseItem).wait()
insertItem
操作将尝试在 DynamoDB 表中创建以下行-
默认情况下,如果已存在具有相同分区键和排序键的项目,则此操作将失败。
注意: StandardCompositePrimaryKey
会将分区键放置在名为 PK 的属性中,并将排序键放置在名为 SK 的属性中。 可以通过降级到基础 CompositePrimaryKey
类型和 PrimaryKeyAttributes
协议来使用自定义分区和排序键属性名称。
可以使用以下方法从 DynamoDB 表中检索项目-
let retrievedItem: StandardTypedDatabaseItem<PayloadType>? = try table.getItem(forKey: key).wait()
getItem
操作返回一个可选的 TypedDatabaseItem
,如果该项目在表中不存在,则该项将为 nil。 如果数据库行中记录的 RowType 与所请求的类型不匹配,这些操作也会失败。
可以使用以下方法在 DynamoDB 表中更新项目-
let updatedPayload = PayloadType(firstly: "firstlyX2", secondly: "secondlyX2")
let updatedDatabaseItem = retrievedItem.createUpdatedItem(withValue: updatedPayload)
try table.updateItem(newItem: updatedDatabaseItem, existingItem: retrievedItem).wait()
updateItem
(或 updateItem
)操作将尝试在 DynamoDB 表中插入以下行-
默认情况下,如果表中不存在具有相同分区键和排序键的项目,并且现有行的版本号与操作中提交的 existingItem
的版本号不相同,则此操作将失败。 DynamoDBCompositePrimaryKeyTable
协议还提供了 clobberItem
操作,该操作将覆盖数据库中的行,而不管现有行如何。
conditionallyUpdateItem
操作将尝试更新主项目,重复调用 updatedPayloadProvider
以检索当前行值的更新版本,直到 update
操作成功为止。 updatedPayloadProvider
可以抛出一个异常,指示当前行值无法更新。
try table.conditionallyUpdateItem(forKey: key, updatedPayloadProvider: updatedPayloadProvider).wait()
conditionallyUpdateItem
操作也可以使用 updatedItemProvider
提供。 它将尝试更新主项目,重复调用 updatedItemProvider
以检索当前行的更新版本,直到 update
操作成功为止。 updatedItemProvider
可以抛出一个异常,指示当前行无法更新。
try table.conditionallyUpdateItem(forKey: key, updatedItemProvider: updatedItemProvider).wait()
可以使用以下方法在 DynamoDB 表中删除项目-
try table.deleteItem(forKey: key).wait()
即使指定的行在数据库表中不存在,deleteItem
操作也会成功。
可以使用查询来检索分区中的所有行或一部分行-
enum TestPolymorphicOperationReturnType: PolymorphicOperationReturnType {
typealias AttributesType = StandardPrimaryKeyAttributes
static var types: [(Codable.Type, PolymorphicOperationReturnOption<StandardPrimaryKeyAttributes, Self>)] = [
(TypeA.self, .init( {.typeA($0)} )),
(TypeB.self, .init( {.typeB($0)} )),
]
case typeA(StandardTypedDatabaseItem<TypeA>)
case typeB(StandardTypedDatabaseItem<TypeB>)
}
let (queryItems, nextPageToken): ([TestPolymorphicOperationReturnType], String?) =
try table.query(forPartitionKey: partitionId,
sortKeyCondition: nil,
limit: 100,
exclusiveStartKey: exclusiveStartKey).wait()
for item in queryItems {
switch item {
case .typeA(let databaseItem):
...
case .typeB(let databaseItem):
}
}
PolymorphicOperationReturnType
类型中指定的行,则 query
操作将失败。query
操作返回的可选字符串可用作另一个请求中的 exclusiveStartKey
,以从 DynamoDB 检索下一“页”结果。query
操作的重载版本,不接受 limit
或 exclusiveStartKey
。 此重载将在内部处理 API 分页,并在必要时多次调用 DynamoDB。一个类似的操作利用 DynamoDB 的 BatchGetItem API,在字典中返回项目,该字典以提供的 CompositePrimaryKey
实例为键-
let batch: [StandardCompositePrimaryKey: TestPolymorphicOperationReturnType] = try table.getItems(forKeys: [key1, key2]).wait()
guard case .testTypeA(let retrievedDatabaseItem1) = batch[key1] else {
...
}
guard case .testTypeB(let retrievedDatabaseItem2) = batch[key2] else {
...
}
如果表在初始请求期间没有容量,此操作将自动处理重试未处理的项目(使用指数退避)。
除了 query
操作之外,还有一组单独的操作,当查询仅检索相同类型的行时,它们提供更简单的 API。
let (queryItems, nextPageToken): ([StandardTypedDatabaseItem<TestTypeA>], String?) =
try table.monomorphicQuery(forPartitionKey: "partitionId",
sortKeyCondition: nil,
limit: 100,
exclusiveStartKey: exclusiveStartKey).wait()
for databaseItem in queryItems {
...
}
还有一个等效的 monomorphicGetItems
DynamoDB 的 BatchGetItem API-
let batch: [StandardCompositePrimaryKey: StandardTypedDatabaseItem<TestTypeA>]
= try table.monomorphicGetItems(forKeys: [key1, key2]).wait()
guard let retrievedDatabaseItem1 = batch[key1] else {
...
}
guard let retrievedDatabaseItem2 = batch[key2] else {
...
}
根据您是否具有任何投影属性,有两种机制可以在索引上进行查询。
如果您正在投影所有属性或某些属性(要使此选项有效,您必须至少投影由 smoke-dynamodb
直接管理的属性,它们是 CreateDate
、LastUpdatedDate
、RowType
和 RowVersion
),您可以像往常一样使用 DynamoDBCompositePrimaryKeyTable
协议及其符合的类型,但使用自定义的 PrimaryKeyAttributes
类型-
public struct GSI1PrimaryKeyAttributes: PrimaryKeyAttributes {
public static var partitionKeyAttributeName: String {
return "GSI-1-PK"
}
public static var sortKeyAttributeName: String {
return "GSI-1-SK"
}
public static var indexName: String? {
return "GSI-1"
}
}
enum TestPolymorphicOperationReturnType: PolymorphicOperationReturnType {
typealias AttributesType = GSI1PrimaryKeyAttributes
static var types: [(Codable.Type, PolymorphicOperationReturnOption<GSI1PrimaryKeyAttributes, Self>)] = [
(TypeA.self, .init( {.typeA($0)} )),
(TypeB.self, .init( {.typeB($0)} )),
]
case typeA(StandardTypedDatabaseItem<TypeA>)
case typeB(StandardTypedDatabaseItem<TypeB>)
}
let (queryItems, nextPageToken): ([TestPolymorphicOperationReturnType], String?) =
try table.query(forPartitionKey: partitionId,
sortKeyCondition: nil,
limit: 100,
exclusiveStartKey: exclusiveStartKey).wait()
for item in queryItems {
switch item {
case .typeA(let databaseItem):
...
case .typeB(let databaseItem):
}
}
对于单态查询也类似-
let (queryItems, nextPageToken): ([TypedDatabaseItem<GSI1PrimaryKeyAttributes, TestTypeA>], String?) =
try table.monomorphicQuery(forPartitionKey: "partitionId",
sortKeyCondition: nil,
limit: 100,
exclusiveStartKey: exclusiveStartKey).wait()
for databaseItem in queryItems {
...
}
要简单地查询没有投影属性的索引上的分区,您可以使用 DynamoDBCompositePrimaryKeysProjection
协议和符合的类型,例如 AWSDynamoDBCompositePrimaryKeysProjection
。 此类型使用生成器类以与主表类型相同的方式创建-
let generator = AWSDynamoDBCompositePrimaryKeysProjectionGenerator(
credentialsProvider: credentialsProvider, region: region,
endpointHostName: dynamodbEndpointHostName, tableName: dynamodbTableName)
let projection = generator.with(logger: logger)
然后可以使用此协议提供的函数来检索分区中的键列表-
let (queryItems, nextPageToken): ([CompositePrimaryKey<GSI1PrimaryKeyAttributes>], String?) =
try projection.query(
forPartitionKey: "partitionId",
sortKeyCondition: nil,
limit: 100,
exclusiveStartKey: exclusiveStartKey).wait()
for primaryKey in queryItems {
...
}
您可以使用批量写入或 事务 写入来写入多个数据库行-
typealias TestTypeAWriteEntry = StandardWriteEntry<TestTypeA>
typealias TestTypeBWriteEntry = StandardWriteEntry<TestTypeB>
enum TestPolymorphicWriteEntry: PolymorphicWriteEntry {
case testTypeA(TestTypeAWriteEntry)
case testTypeB(TestTypeBWriteEntry)
func handle<Context: PolymorphicWriteEntryContext>(context: Context) throws -> Context.WriteEntryTransformType {
switch self {
case .testTypeA(let writeEntry):
return try context.transform(writeEntry)
case .testTypeB(let writeEntry):
return try context.transform(writeEntry)
}
}
}
let entryList: [TestPolymorphicWriteEntry] = [
.testTypeA(.insert(new: databaseItem1)),
.testTypeB(.insert(new: databaseItem2))
]
try await table.bulkWrite(entryList)
try await table.transactWrite(entryList)
对于事务,您可以另外指定一组约束作为事务的一部分-
typealias TestTypeAStandardTransactionConstraintEntry = StandardTransactionConstraintEntry<TestTypeA>
typealias TestTypeBStandardTransactionConstraintEntry = StandardTransactionConstraintEntry<TestTypeB>
enum TestPolymorphicTransactionConstraintEntry: PolymorphicTransactionConstraintEntry {
case testTypeA(TestTypeAStandardTransactionConstraintEntry)
case testTypeB(TestTypeBStandardTransactionConstraintEntry)
func handle<Context: PolymorphicWriteEntryContext>(context: Context) throws -> Context.WriteTransactionConstraintType {
switch self {
case .testTypeA(let writeEntry):
return try context.transform(writeEntry)
case .testTypeB(let writeEntry):
return try context.transform(writeEntry)
}
}
}
let constraintList: [TestPolymorphicTransactionConstraintEntry] = [
.testTypeA(.required(existing: databaseItem3)),
.testTypeB(.required(existing: databaseItem4))
]
try await table.transactWrite(entryList, constraints: constraintList)
PolymorphicWriteEntry
和 PolymorphicTransactionConstraintEntry
符合的类型可以选择性地提供一个 compositePrimaryKey
属性,该属性允许 API 返回有关失败事务的更多信息。
enum TestPolymorphicWriteEntry: PolymorphicWriteEntry {
case testTypeA(TestTypeAWriteEntry)
case testTypeB(TestTypeBWriteEntry)
func handle<Context: PolymorphicWriteEntryContext>(context: Context) throws -> Context.WriteEntryTransformType {
switch self {
case .testTypeA(let writeEntry):
return try context.transform(writeEntry)
case .testTypeB(let writeEntry):
return try context.transform(writeEntry)
}
}
var compositePrimaryKey: StandardCompositePrimaryKey? {
switch self {
case .testTypeA(let writeEntry):
return writeEntry.compositePrimaryKey
case .testTypeA(let writeEntry):
return writeEntry.compositePrimaryKey
}
}
}
此包包含许多便捷函数,用于在历史分区中存储行的版本
insertItemWithHistoricalRow
操作提供了一个调用来插入主项和历史项-
try table.insertItemWithHistoricalRow(primaryItem: databaseItem, historicalItem: historicalItem).wait()
updateItemWithHistoricalRow
操作提供了一个调用来更新主项并插入历史项-
try table.updateItemWithHistoricalRow(primaryItem: updatedItem,
existingItem: databaseItem,
historicalItem: historicalItem).wait()
clobberItemWithHistoricalRow
操作将尝试插入或更新主项,重复调用 primaryItemProvider
以检索当前行(如果存在)的更新版本,直到相应的 insert
或 update
操作成功。 调用 historicalItemProvider
以基于插入到数据库表中的主项提供历史项。 主项可能一开始不存在于数据库表中。
try table.clobberItemWithHistoricalRow(primaryItemProvider: primaryItemProvider,
historicalItemProvider: historicalItemProvider).wait()
clobberItemWithHistoricalRow
操作通常用于以下情况:当您不确定主条目是否已存在于数据库表中,并且您希望插入该条目或写入该行的新版本(无论是否基于现有条目)。
如果 insert
或 update
操作重复失败(默认是在 10 次尝试后),此操作可能会因并发错误而失败。
conditionallyUpdateItemWithHistoricalRow
操作将尝试更新主条目,并重复调用 primaryItemProvider
以检索当前行的更新版本,直到 update
操作成功。primaryItemProvider
可以抛出异常,表明当前行无法更新。historicalItemProvider
会被调用,以基于已插入数据库表中的主条目提供历史条目。
try table.conditionallyUpdateItemWithHistoricalRow(
forPrimaryKey: dKey,
primaryItemProvider: conditionalUpdatePrimaryItemProvider,
historicalItemProvider: conditionalUpdateHistoricalItemProvider).wait()
conditionallyUpdateItemWithHistoricalRow
操作通常用于以下情况:已知主条目存在,并且您希望基于其当前版本的某些属性测试是否可以更新它。一个常见的场景是将从属相关条目添加到主条目,其中相关条目的数量有限制。在这种情况下,您需要测试主条目的当前版本,以确保相关条目的数量没有超出限制。
如果 update
操作重复失败(默认是在 10 次尝试后),此操作可能会因并发错误而失败。
注意: clobberItemWithHistoricalRow
操作本质上类似,但用例略有不同。 clobber
操作通常用于创建或更新主条目。 conditionallyUpdate
操作通常用于创建需要检查主条目是否可以更新的从属相关条目。
clobberVersionedItemWithHistoricalRow
操作提供了一种机制,用于管理可变的数据库行,并将该行的所有先前版本存储在历史分区中。此操作将主条目存储在“版本零”排序键下,其有效负载复制了该行的当前版本。此历史分区包含每个版本的行,包括该版本排序键下的当前版本。
let payload1 = PayloadType(firstly: "firstly", secondly: "secondly")
let partitionKey = "partitionId"
let historicalPartitionPrefix = "historical"
let historicalPartitionKey = "\(historicalPartitionPrefix).\(partitionKey)"
func generateSortKey(withVersion version: Int) -> String {
let prefix = String(format: "v%05d", version)
return [prefix, "sortId"].dynamodbKey
}
try table.clobberVersionedItemWithHistoricalRow(forPrimaryKey: partitionKey,
andHistoricalKey: historicalPartitionKey,
item: payload1,
primaryKeyType: StandardPrimaryKeyAttributes.self,
generateSortKey: generateSortKey).wait()
// the v0 row, copy of version 1
let key1 = StandardCompositePrimaryKey(partitionKey: partitionKey, sortKey: generateSortKey(withVersion: 0))
let item1: StandardTypedDatabaseItem<RowWithItemVersion<PayloadType>> = try table.getItem(forKey: key1).wait()
item1.rowValue.itemVersion // 1
item1.rowStatus.rowVersion // 1
item1.rowValue.rowValue // payload1
// the v1 row, has version 1
let key2 = StandardCompositePrimaryKey(partitionKey: historicalPartitionKey, sortKey: generateSortKey(withVersion: 1))
let item2: StandardTypedDatabaseItem<RowWithItemVersion<PayloadType>> = try table.getItem(forKey: key2).wait()
item1.rowValue.itemVersion // 1
item1.rowStatus.rowVersion // 1
item1.rowValue.rowValue // payload1
let payload2 = PayloadType(firstly: "thirdly", secondly: "fourthly")
try table.clobberVersionedItemWithHistoricalRow(forPrimaryKey: partitionKey,
andHistoricalKey: historicalPartitionKey,
item: payload2,
primaryKeyType: StandardPrimaryKeyAttributes.self,
generateSortKey: generateSortKey).wait()
// the v0 row, copy of version 2
let key3 = StandardCompositePrimaryKey(partitionKey: partitionKey, sortKey: generateSortKey(withVersion: 0))
let item3: StandardTypedDatabaseItem<RowWithItemVersion<PayloadType>> = try table.getItem(forKey: key3).wait()
item1.rowValue.itemVersion // 2
item1.rowStatus.rowVersion // 2
item1.rowValue.rowValue // payload2
// the v1 row, still has version 1
let key4 = StandardCompositePrimaryKey(partitionKey: historicalPartitionKey, sortKey: generateSortKey(withVersion: 1))
let item4: StandardTypedDatabaseItem<RowWithItemVersion<PayloadType>> = try table.getItem(forKey: key4).wait()
item1.rowValue.itemVersion // 1
item1.rowStatus.rowVersion // 1
item1.rowValue.rowValue // payload1
// the v2 row, has version 2
let key5 = StandardCompositePrimaryKey(partitionKey: historicalPartitionKey, sortKey: generateSortKey(withVersion: 2))
let item5: StandardTypedDatabaseItem<RowWithItemVersion<PayloadType>> = try table.getItem(forKey: key5).wait()
item1.rowValue.itemVersion // 2
item1.rowStatus.rowVersion // 1
item1.rowValue.rowValue // payload2
这为更新数据库表中的可变行提供了一种本地化的同步机制,其中锁被跟踪为主条目的 rowVersion。 这允许安全地更新版本化的可变行,并且对不同主条目的更新不会争用全表锁定。
DynamoDB 生存时间功能通过向条目添加每个条目的时间戳属性来支持。 在指定的时间戳的日期和时间后不久,DynamoDB 会从您的表中删除该条目。 有关更多详细信息和启用 TTL 的说明,请查看 此处。
可以使用以下方法将带有 TTL 的条目插入到 DynamoDB 表中 -
struct PayloadType: Codable, Equatable {
let firstly: String
let secondly: String
}
let key = StandardCompositePrimaryKey(partitionKey: "partitionId",
sortKey: "sortId")
let payload = PayloadType(firstly: "firstly", secondly: "secondly")
let timeToLive = StandardTimeToLive(timeToLiveTimestamp: 123456789)
let databaseItem = StandardTypedDatabaseItem.newItem(withKey: key, andValue: payload, andTimeToLive: timeToLive)
try table.insertItem(databaseItem).wait()
insertItem
操作将尝试在 DynamoDB 表中创建以下行-
默认情况下,如果已存在具有相同分区键和排序键的项目,则此操作将失败。
注意
StandardCompositePrimaryKey
会将分区键放在名为 *PK* 的属性中,并将排序键放在名为 *SK* 的属性中。 通过下降到基础的 CompositePrimaryKey
类型和 PrimaryKeyAttributes
协议,可以使用自定义分区和排序键属性名称。
StandardTimeToLive
会将 TTL 时间戳放在名为 *ExpireDate* 的属性中。 通过下降到基础的 TimeToLive
类型和 TimeToLiveAttributes
协议,可以使用自定义 TTL 时间戳属性名称。
可以使用以下方法在 DynamoDB 表中更新带有 TTL 的条目 -
let updatedPayload = PayloadType(firstly: "firstlyX2", secondly: "secondlyX2")
let updatedTimeToLive = StandardTimeToLive(timeToLiveTimestamp: 234567890)
let updatedDatabaseItem = retrievedItem.createUpdatedItem(withValue: updatedPayload, andTimeToLive: updatedTimeToLive)
try table.updateItem(newItem: updatedDatabaseItem, existingItem: retrievedItem).wait()
updateItem
(或 updateItem
)操作将尝试在 DynamoDB 表中插入以下行-
默认情况下,如果表中不存在具有相同分区键和排序键的项目,并且现有行的版本号与操作中提交的 existingItem
的版本号不相同,则此操作将失败。 DynamoDBCompositePrimaryKeyTable
协议还提供了 clobberItem
操作,该操作将覆盖数据库中的行,而不管现有行如何。
此软件包提供的主要实体是
TypedDatabaseItemWithTimeToLive
,带有默认的 StandardTimeToLiveAttributes
泛型类型,用于向后兼容。InMemoryDynamoDBCompositePrimaryKeyTable
*:一个结构体,符合 DynamoDBCompositePrimaryKeyTable
协议,并与本地内存表交互。DynamoDBCompositePrimaryKeyTable
协议,并与 AWS DynamoDB 服务交互。CompositePrimaryKey 结构体定义了数据库中行的分区和排序键值。 它还用于序列化和反序列化这些值。 为了方便起见,此软件包提供了一个类型别名,称为 StandardCompositePrimaryKey
,它使用一个分区键,其属性名称为 *PK*,一个排序键,其属性名称为 *SK*。 可以按如下所示实例化此结构体 -
let key = StandardCompositePrimaryKey(partitionKey: "partitionKeyValue",
sortKey: "sortKeyValue")
TimeToLive 结构体定义了数据库中行的 TTL 时间戳值。 它还用于序列化和反序列化此值。 为了方便起见,此软件包提供了一个类型别名,称为 StandardTimeToLive
,它使用 TTL 时间戳,其属性名称为 *ExpireDate*。 可以按如下所示实例化此结构体 -
let timeToLive = StandardTimeToLive(timeToLiveTimestamp: 123456789)
TypedDatabaseItemWithTimeToLive 结构体管理数据库表中的多个属性,以启用将行解码和编码为正确的类型以及从正确的类型解码和编码行。 此外,它还管理其他便利功能,例如版本控制。 此结构体将添加到数据库行的属性是 -
与 CompositePrimaryKey 类似,此软件包提供一个类型别名,称为 StandardTypedDatabaseItem
,它需要标准分区、排序键和 TTL 属性名称。
可以按如下所示实例化此结构体 -
let newDatabaseItem = StandardTypedDatabaseItem.newItem(withKey: compositePrimaryKey, andValue: rowValueType)
或者带有 TTL -
let newDatabaseItem = StandardTypedDatabaseItem.newItem(withKey: compositePrimaryKey, andValue: rowValueType, andTimeToLive: timeToLive)
在此,*compositePrimaryKey* 必须是 CompositePrimaryKey
类型,*rowValueType* 必须符合 Codable 协议,并且 *timeToLive* 必须是 TimeToLive
类型。 默认情况下,对此项执行 **PutItem** 操作,而该条目在表中已存在,则该操作将失败。
此结构体上的 *createUpdatedItem* 函数可用于创建此行的更新版本 -
let updatedDatabaseItem = newDatabaseItem.createUpdatedItem(withValue: updatedValue)
或者带有 TTL -
let updatedDatabaseItem = newDatabaseItem.createUpdatedItem(withValue: updatedValue, andTimeToLive: updatedTimeToLive)
此函数将创建一个新的 TypedDatabaseItemWithTimeToLive 实例,该实例具有相同的键和更新的 LastUpdatedDate 和 RowVersion 值。 默认情况下,对此项执行 **PutItem** 操作,而该条目在表中已存在,并且 RowVersion 不等于原始行的值,则该操作将失败。
DynamoDBCompositePrimaryKeyTable
协议提供了许多用于与 DynamoDB 表交互的函数。 通常,使用 CredentialProvider
(例如来自 smoke-aws-credentials
模块的 CredentialProvider 来自动处理轮换凭证)、服务区域和端点以及要使用的表名称来实例化此协议的 AWSDynamoDBCompositePrimaryKeyTable
实现。
let generator = AWSDynamoDBCompositePrimaryKeyTableGenerator(
credentialsProvider: credentialsProvider, region: region,
endpointHostName: dynamodbEndpointHostName, tableName: dynamodbTableName)
let table = generator.with(logger: logger)
在内部,AWSDynamoDBCompositePrimaryKeyTable
使用自定义的 Decoder 和 Encoder 来序列化符合 Codable
的类型,以及序列化为 DynamoDB 服务所需的 JSON 模式并从中进行序列化。 这些 Decoder 和 Encoder 实现会自动将属性名称大写。
CompositePrimaryKey
、TypedDatabaseItemWithTimeToLive
和 PolymorphicDatabaseItem
对于符合 PrimaryKeyAttributes
协议的类型都是通用的。 此协议可用于为分区和排序键使用自定义属性名称。
public struct MyPrimaryKeyAttributes: PrimaryKeyAttributes {
public static var partitionKeyAttributeName: String {
return "MyPartitionAttributeName"
}
public static var sortKeyAttributeName: String {
return "MySortKeyAttributeName"
}
}
TimeToLive
、TypedDatabaseItemWithTimeToLive
和 PolymorphicDatabaseItem
对于符合 TimeToLiveAttributes
协议的类型都是通用的。 此协议可用于为 TTL 时间戳使用自定义属性名称。
public struct MyTimeToLiveAttributes: TimeToLiveAttributes {
public static var timeToLiveAttributeName: String {
return "MyTimeToLiveAttributeName"
}
}
如果用于行类型的 Codable
类型也符合 CustomRowTypeIdentifier
,则此类型的 *rowTypeIdentifier* 属性将用作记录在数据库行中的 RowType。
struct TypeB: Codable, CustomRowTypeIdentifier {
static var rowTypeIdentifier: String? = "TypeBCustom"
let thirdly: String
let fourthly: String
}
RowWithIndex 是一个辅助结构体,它提供索引(例如 GSI)属性作为数据库行类型的一部分。
RowWithItemVersion 是一个辅助结构体,它提供“ItemVersion”,以便与历史条目扩展一起使用。
此库在 Apache 2.0 许可证下获得许可。