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

Adding Transactions #92

Merged
merged 48 commits into from
May 3, 2022
Merged

Conversation

nhyne
Copy link
Collaborator

@nhyne nhyne commented Jan 27, 2022

Adding transaction functionality. A user can now build a DynamoDBQuery and call .transaction on it to have it be executed as a single transaction. Currently if there are problems with the transaction, like mixing Get and Write actions, the user will get a failing ZIO effect. There is also a safeTransaction method that returns an Either[Throwable, DynamoDBQuery[A]] for type safe transactions. I'm happy to change that method name if someone has a different suggestion.

Copy link
Contributor

@googley42 googley42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! You have shown that the usage patterns for TX that I assumed when creating the ticket are in fact different to the normal auto-batching use case for Gets/Puts/Deletes and I agree with your approach.

I have a suggestion for capturing the difference in functionality with a different trait hierarchy (not sure if it'll work though)

It may be worth running the whole thing by John and getting his feedback.

Great work!

@nhyne nhyne changed the title Adding TransactWriteItems API Adding Transactions Apr 13, 2022
@nhyne nhyne marked this pull request as ready for review April 24, 2022 17:17
Copy link
Contributor

@googley42 googley42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome work! This provides super ergonomic transaction semantics.

Some really minor comments mainly around the tests.

@@ -200,6 +200,27 @@ object LiveSpec extends DefaultRunnableSpec {
withDefaultTable { tableName =>
getItem(tableName, PrimaryKey(id -> "nowhere", number -> 1000)).execute.map(item => assert(item)(isNone))
}
},
testM("batch get item") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about

Suggested change
testM("batch get item") {
testM("batch get item with a transaction") {

? or move it to
suite("transactions") below?

Copy link
Collaborator Author

@nhyne nhyne Apr 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was actually supposed to just be a batch get instead of a batch get transaction. I noticed we didn't have an integration test for the batch gets. I'll remove the .transaction from it.

dynamodb/src/it/scala/zio/dynamodb/LiveSpec.scala Outdated Show resolved Hide resolved
testM("batch get item") {
withDefaultTable { tableName =>
val getItems = BatchGetItem().addAll(
GetItem(TableName(tableName), Item(id -> first, number -> 7)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ATM we have to mentally track that Item(id -> first, number -> 7) is the PK of avi3Item.
How about creating a utility function for the whole suite, something like def pk(item: Item): PrimaryKey = ??? as I think id and number are the primary keys for the test data used throughout the suite. This line would then become:

Suggested change
GetItem(TableName(tableName), Item(id -> first, number -> 7)),
GetItem(TableName(tableName), pk(avi3Item)),

so we end up a direct a correlation between the item and its primary key.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have a weird implementation that has a dummy PK for its default, let me know what you think.

Copy link
Contributor

@googley42 googley42 Apr 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think its a big improvement on readability. WRT the implementation I was thinking of a direct transformation of the input eg something like

private def pk(item: Item): PrimaryKey =
  (item.map.get("id"), item.map.get("num")) match {
    case (Some(id), Some(num)) => PrimaryKey("id" -> id, "num" -> num)
    case _                     => throw new IllegalStateException(s"Both id and num need to present in item $item")
  }

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update this tonight before merging

)

for {
_ <- conditionCheck.zip(putItem).transaction.execute
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice semantics - I like it!

val conditionCheck = ConditionCheck(
primaryKey = PrimaryKey(id -> first, number -> 7),
tableName = TableName(tableName),
conditionExpression = ConditionExpression.Equals(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about extracting this to a val called something like conditionAlwaysTrue = ???

.foldLeft(headConstructor: Either[Throwable, TransactionType]) {
case (acc, constructor) =>
acc match {
case l @ Left(_) => l // Should also continue collecting other failures
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about creating an issue for collecting failures so we can track this (collecting multiple failures)? I think zio-aws for ZIO 2.0 is bringing in zio-prelude as a transitive dependency anyway so maybe we should consider zio-prelude Validation internally (I have used it in production and the ergonomics are really nice)

billingMode = BillingMode.PayPerRequest
).transaction.execute
)(equalTo(()))
} @@ failing,
Copy link
Contributor

@googley42 googley42 Apr 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again it would be nice to zoom in on the specific exception type rather than use catch all failing (and perhaps some small sub-string of the message we are expecting) eg something like

  def assertDynamoDbException(substring: String) =
    isSubtype[DynamoDbException](hasMessage(containsString(substring)))

  val suiteExperiment = suite("experiment")(testM("foo") {
    val program = ZIO.effect(throw DynamoDbException.builder.message("Transaction gone BOOOOOOM!").build)

    assertM(program.run)(fails(assertDynamoDbException("BOOOOOOM")))
  })

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated all of these failing to be more specific as well

@googley42
Copy link
Contributor

PS from now on we will also have to create an issue to copy over PR changes once merged to the ZIO 2 branch

@nhyne
Copy link
Collaborator Author

nhyne commented Apr 27, 2022

I created two issues, one for collecting all of the invalid transaction items and another for merging the transaction changes into the zio2 branch.

Copy link
Contributor

@googley42 googley42 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work!

@nhyne nhyne merged commit a094262 into zio:master May 3, 2022
@nhyne nhyne deleted the nhyne/transaction-write-items branch May 3, 2022 17:05
@nhyne nhyne restored the nhyne/transaction-write-items branch May 3, 2022 17:05
@nhyne nhyne deleted the nhyne/transaction-write-items branch May 3, 2022 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants