Build - Main Branch Swift 5.6, 5.7 and 5.8 Tested Ubuntu 18.04 and 20.04 Tested CentOS 8 Tested Amazon Linux 2 Tested Join the Smoke Server Side community on gitter Apache 2

SmokeDynamoDB 是一个库,旨在简化从基于 Swift 的应用程序中使用 DynamoDB 的过程,尤其侧重于多态数据库表(即并非所有行都具有单一 schema 的表)的使用。

入门指南

步骤 1:添加 SmokeDynamoDB 依赖项

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 协议的类型对 DynamoDB 表执行操作。 在生产环境中,可以使用 AWSDynamoDBCompositePrimaryKeyTable 执行操作。

对于基本用例,您可以使用 tableName、credentialsProvider 和 awsRegion 初始化一个表。

let table = AWSDynamoDBCompositePrimaryKeyTable(tableName: tableName,
                credentialsProvider: credentialsProvider,
                awsRegion: awsRegion)
                
...
                
try await table.shutdown()

此类的初始化器还可以接受可选参数,例如 LoggerEventLoopInternalRequestId 来使用。

传递 EventLoop 对于也使用 SwiftNIO 作为服务器的应用程序非常有用,并且希望在与服务器的传入请求相同的 EventLoop 上处理下游服务调用。

为了在实例之间共享客户端配置或底层 http 客户端(例如在基于请求的服务中,您可能希望为每个请求创建一个实例),还可以使用 AWSDynamoDBClientConfigurationAWSDynamoDBTableOperationsClient 类型-

一种选择是创建一次配置(例如在应用程序启动时)-

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* 类型提供了一个包装器,围绕着另一个表,并在访问之间模拟对该表的其他写入。 这些类型旨在允许对表并发处理进行单元测试。

DynamoDB Local

使用 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):
    }
}
  1. 排序键条件可以将查询限制为分区行的一部分。 nil 条件将返回分区中的所有行。
  2. 如果分区包含未在输出 PolymorphicOperationReturnType 类型中指定的行,则 query 操作将失败。
  3. query 操作返回的可选字符串可用作另一个请求中的 exclusiveStartKey,以从 DynamoDB 检索下一“页”结果。
  4. 有一个 query 操作的重载版本,不接受 limitexclusiveStartKey。 此重载将在内部处理 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 直接管理的属性,它们是 CreateDateLastUpdatedDateRowTypeRowVersion),您可以像往常一样使用 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)

PolymorphicWriteEntryPolymorphicTransactionConstraintEntry 符合的类型可以选择性地提供一个 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 以检索当前行(如果存在)的更新版本,直到相应的 insertupdate 操作成功。 调用 historicalItemProvider 以基于插入到数据库表中的主项提供历史项。 主项可能一开始不存在于数据库表中。

try table.clobberItemWithHistoricalRow(primaryItemProvider: primaryItemProvider,
                                       historicalItemProvider: historicalItemProvider).wait()

clobberItemWithHistoricalRow 操作通常用于以下情况:当您不确定主条目是否已存在于数据库表中,并且您希望插入该条目或写入该行的新版本(无论是否基于现有条目)。

如果 insertupdate 操作重复失败(默认是在 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。 这允许安全地更新版本化的可变行,并且对不同主条目的更新不会争用全表锁定。

生存时间 (TTL)

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 操作,该操作将覆盖数据库中的行,而不管现有行如何。

实体

此软件包提供的主要实体是

CompositePrimaryKey(复合主键)

CompositePrimaryKey 结构体定义了数据库中行的分区和排序键值。 它还用于序列化和反序列化这些值。 为了方便起见,此软件包提供了一个类型别名,称为 StandardCompositePrimaryKey,它使用一个分区键,其属性名称为 *PK*,一个排序键,其属性名称为 *SK*。 可以按如下所示实例化此结构体 -

let key = StandardCompositePrimaryKey(partitionKey: "partitionKeyValue",
                                      sortKey: "sortKeyValue")

TimeToLive(生存时间)

TimeToLive 结构体定义了数据库中行的 TTL 时间戳值。 它还用于序列化和反序列化此值。 为了方便起见,此软件包提供了一个类型别名,称为 StandardTimeToLive,它使用 TTL 时间戳,其属性名称为 *ExpireDate*。 可以按如下所示实例化此结构体 -

let timeToLive = StandardTimeToLive(timeToLiveTimestamp: 123456789)

TypedDatabaseItemWithTimeToLive

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

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 实现会自动将属性名称大写。

自定义

PrimaryKeyAttributes

CompositePrimaryKeyTypedDatabaseItemWithTimeToLivePolymorphicDatabaseItem 对于符合 PrimaryKeyAttributes 协议的类型都是通用的。 此协议可用于为分区和排序键使用自定义属性名称。

public struct MyPrimaryKeyAttributes: PrimaryKeyAttributes {
    public static var partitionKeyAttributeName: String {
        return "MyPartitionAttributeName"
    }
    public static var sortKeyAttributeName: String {
        return "MySortKeyAttributeName"
    }
}

TimeToLiveAttributes

TimeToLiveTypedDatabaseItemWithTimeToLivePolymorphicDatabaseItem 对于符合 TimeToLiveAttributes 协议的类型都是通用的。 此协议可用于为 TTL 时间戳使用自定义属性名称。

public struct MyTimeToLiveAttributes: TimeToLiveAttributes {
    public static var timeToLiveAttributeName: String {
        return "MyTimeToLiveAttributeName"
    }
}

CustomRowTypeIdentifier

如果用于行类型的 Codable 类型也符合 CustomRowTypeIdentifier,则此类型的 *rowTypeIdentifier* 属性将用作记录在数据库行中的 RowType。

struct TypeB: Codable, CustomRowTypeIdentifier {
    static var rowTypeIdentifier: String? = "TypeBCustom"
    
    let thirdly: String
    let fourthly: String
}

RowWithIndex

RowWithIndex 是一个辅助结构体,它提供索引(例如 GSI)属性作为数据库行类型的一部分。

RowWithItemVersion

RowWithItemVersion 是一个辅助结构体,它提供“ItemVersion”,以便与历史条目扩展一起使用。

许可证

此库在 Apache 2.0 许可证下获得许可。