Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

High Level API cheat-sheet style documentation page #416

Merged
merged 6 commits into from
Apr 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ ZIO DynamoDB is a library that is used for type-safe, efficient, and boilerplate

Under the hood we use the excellent [ZIO AWS](https://zio.dev/zio-aws) library for type-safe DynamoDB access, and the awesome [ZIO Schema](https://zio.dev/zio-schema) library for schema derived codecs (see here for documentation on how to [customise these through annotations](docs/codec-customization.md)).

For an overview of the High Level API please see the [ZIO DynamoDB cheat sheet](docs/cheat-sheet.md).

## Installation

To use ZIO DynamoDB, we need to add the following line to our `build.sbt` file:
Expand Down Expand Up @@ -45,8 +47,6 @@ libraryDependencies ++= Seq(
)
```

For examples please see [examples sbt module](examples/src/main/scala/zio/dynamodb/examples/ZioDynamodbJsonExamples.scala).

## Example

For examples please see examples sbt module. Below is `Main.scala` from that module:
Expand Down
44 changes: 44 additions & 0 deletions docs/cheat-sheet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# High Level API Cheat Sheet

Note this guide assumes the reader has some basic knowledge of [AWS DynamoDB API](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html).

Assuming the below model
```scala
final case class Person(id: String, name: String, year: Int)
object Person {
implicit val schema: Schema.CaseClass3[String, String, Int, Person] = DeriveSchema.gen[Person]
val (id, name, year) = ProjectionExpression.accessors[Person]
}
```

For more detailed working examples please see the High Level API integration tests [crud](../dynamodb/src/it/scala/zio/dynamodb/TypeSafeApiCrudSpec.scala), [mapping](../dynamodb/src/it/scala/zio/dynamodb/TypeSafeApiMappingSpec.scala), [scan and query](../dynamodb/src/it/scala/zio/dynamodb/TypeSafeScanAndQuerySpec.scala), [streaming](../dynamodb/src/it/scala/zio/dynamodb/TypeSafeStreamingUtilsSpec.scala)


| AWS | ZIO DynamoDB |
|-------------------------------| --- |
| [GetItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_GetItem.html) | `person <- get("personTable")(Person.id.partitionKey === "1").execute` |
| [UpdateItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html) | `_ <- update("personTable")(Person.id.partitionKey === "1")(Person.name.set("Foo")).execute` |
| [PutItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html) | _ <- `put("personTable", Person(42, "John", 2020)).execute` |
| [DeleteItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_DeleteItem.html) | `_ <- deleteFrom("personTable")(Person.id.partitionKey === "1").execute` |
| | |
| [Projection Expressions](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.Attributes.html) | `Person.id`, `Person.name`, `Person.year` |
| [Condition Expressions](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ConditionExpressions.html) | `<DynamoDBQuery>.where(Person.id === "1")` |
| **Filter Expressions** apply to Scan and Query | `<DynamoDBQuery>.filter(Person.year > 2020)` |
| [Update Expressions](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html#DDB-UpdateItem-request-UpdateExpression) | `update("personTable")(Person.id.partitionKey === "1")(Person.name.set("John") + Person.year.add(1))` |
| [Primary Keys](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.CoreComponents.html) | `Person.id.partitionKey === "1"` or `Person.id.partitionKey === "1" && Person.year.sortKey === 2020` if table has both partition _and_ sort keys |
| [Key Condition Expressions](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-KeyConditionExpression) | `<query>.whereKey(Person.id.partitionKey === "1" && Person.year.sortKey > 2020)` |
| [_Expression Attribute Names_](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ExpressionAttributeNames.html) | _Managed automatically!_ |
| [_Expression Attribute Values_](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ExpressionAttributeValues.html) | _Managed automatically!_ |
| | |
| [Scan](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html) | `stream <- scanAll[Person]("personTable").execute`
| [Scan with parallel processing](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html) | `stream <- scanAll[Person]("personTable").parallel(42).execute`
| [Scan with paging](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html) | `(people, lastEvaluatedKey) <- scanSome[Person]("personTable", limit = 5).startKey(oldLastEvaluatedKey).execute`
| [Query](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html) | `stream <- queryAll[Person]("personTable").whereKey(Person.name.contains("mi")).execute`
| [Query with paging](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html) | `(people, lastEvaluatedKey) <- querySome[Person]("personTable", limit = 5).whereKey(Person.name.contains("mi"))`
| | |
| [BatchGetItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html) | `people <- DynamoDBQuery.forEach(listOfIds)(id => DynamoDBQuery.get[Person]("personTable")(Person.id.partitionKey === id)).execute`|
| [BatchWriteItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html) | _ <- `DynamoDBQuery.forEach(people)(p => put("personTable", p)).execute` |
| | |
| [TransactGetItems](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactGetItems.html) | `val getJohn = get("personTable")(Person.id.partitionKey === "1")`<br>`val getSmith = get("personTable")(Person.id.partitionKey === "2")`<br>`tuple <- (getJohn zip getSmith).transaction.execute` |
| [TransactWriteItems](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_TransactWriteItems.html) | `val putJohn = put("personTable", Person(1, "John", 2020))`<br>`val putSmith = put("personTable", Person(2, "Smith", 2024))`<br>`_ <- (putJohn zip putSmith).transaction.execute` |

47 changes: 44 additions & 3 deletions dynamodb/src/it/scala/zio/dynamodb/TypeSafeApiCrudSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import zio.Chunk
import software.amazon.awssdk.services.dynamodb.model.ConditionalCheckFailedException
import zio.stream.ZStream
import zio.ZIO
import software.amazon.awssdk.services.dynamodb.model.TransactionCanceledException

object TypeSafeApiCrudSpec extends DynamoDBLocalSpec {

Expand Down Expand Up @@ -40,7 +41,13 @@ object TypeSafeApiCrudSpec extends DynamoDBLocalSpec {
}

override def spec =
suite("TypeSafeApiCrudSpec")(putSuite, updateSuite, deleteSuite, forEachSuite) @@ TestAspect.nondeterministic
suite("TypeSafeApiCrudSpec")(
putSuite,
updateSuite,
deleteSuite,
forEachSuite,
transactionSuite
) @@ TestAspect.nondeterministic

private val putSuite =
suite("put")(
Expand All @@ -49,7 +56,7 @@ object TypeSafeApiCrudSpec extends DynamoDBLocalSpec {
val originalPerson = Person("1", "Smith", Some("John"), 21)
val updatedPerson = Person("1", "Smith", Some("Smith"), 42)
for {
_ <- put(tableName, originalPerson).returns(ReturnValues.AllOld).execute
_ <- put(tableName, originalPerson).execute
rtrn <- put(tableName, updatedPerson).returns(ReturnValues.AllOld).execute
updated <- get(tableName)(Person.id.partitionKey === "1").execute.absolve
} yield assertTrue(rtrn == Some(originalPerson) && updated == updatedPerson)
Expand Down Expand Up @@ -754,7 +761,7 @@ object TypeSafeApiCrudSpec extends DynamoDBLocalSpec {
test("with a put query") {
withSingleIdKeyTable { tableName =>
val person1 = Person("1", "Smith", Some("John"), 21)
val person2 = Person("2", "Tarlochan", Some("Peter"), 42)
val person2 = Person("2", "Jones", Some("Tarlochan"), 42)
for {
_ <- forEach(Chunk(person1, person2))(person => put(tableName, person)).execute
stream <- scanAll[Person](tableName).execute
Expand Down Expand Up @@ -794,4 +801,38 @@ object TypeSafeApiCrudSpec extends DynamoDBLocalSpec {
}
)

val transactionSuite = suite("transactions")(
test("multiple get queries succeed (ie no AWS failures) within a transaction") {
withSingleIdKeyTable { tableName =>
val person1 = Person("1", "Smith", Some("John"), 21)
val person2 = Person("2", "Jones", Some("Tarlochan"), 42)
val getJohn = get(tableName)(Person.id.partitionKey === "1")
val getTarlochan = get(tableName)(Person.id.partitionKey === "2")
for {
_ <- forEach(Chunk(person1, person2))(person => put(tableName, person)).execute
result <- (getJohn zip getTarlochan).transaction.execute.either
} yield assert(result)(isRight(equalTo((Right(person1), Right(person2)))))
}
},
test("multiple puts are atomic within a transaction") {
withSingleIdKeyTable { tableName =>
val person1 = Person("1", "Smith", Some("John"), 21)
val person2 = Person("2", "Jones", Some("Peter"), 42)
val putPerson1 = put(tableName, person1.copy(forename = Some("Updated"))).where(Person.id <> "2")
val putPerson2 = put(tableName, person2.copy(forename = Some("Updated"))).where(Person.id <> "2")
for {
_ <- forEach(Chunk(person1, person2))(person => put(tableName, person)).execute
result <- (putPerson1 zip putPerson2).transaction.execute.either
hasTXError = result match {
case Left(DynamoDBError.AWSError(_: TransactionCanceledException)) => true
case _ => false
}
stream <- scanAll[Person](tableName).execute
people <- stream.runCollect
} yield assertTrue(hasTXError) && assertTrue(!people.exists(_.forename == Some("Updated")))
// without the transaction the 1st put would have succeeded
}
}
)

}
Loading