Skip to content

Commit c4f7d1d

Browse files
authored
Merge pull request #81 from fsprojects/multi-transaction-changes
Multi transaction API changes
2 parents 3a77a31 + 3b9a883 commit c4f7d1d

File tree

5 files changed

+59
-77
lines changed

5 files changed

+59
-77
lines changed

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -269,25 +269,26 @@ let doesntExistCondition = compile <@ fun t -> NOT_EXISTS t.Value @>
269269
let existsCondition = compile <@ fun t -> EXISTS t.Value @>
270270
let key = TableKey.Combined(hashKey, rangeKey)
271271
272-
let transaction = Transaction()
272+
let transaction = table.CreateTransaction()
273273
274274
transaction.Check(table, key, doesntExistCondition)
275275
transaction.Put(table, item2, None)
276276
transaction.Put(table, item3, Some existsCondition)
277-
transaction.Delete (table ,table.Template.ExtractKey item5, None)
277+
transaction.Delete(table, table.Template.ExtractKey item5, None)
278+
278279
do! transaction.TransactWriteItems()
279280
```
280281

281-
Failed preconditions (or `TransactWrite.Check`s) are signalled as per the underlying API: via a `TransactionCanceledException`.
282-
Use `TransactWriteItemsRequest.TransactionCanceledConditionalCheckFailed` to trap such conditions:
282+
Failed preconditions (or `Check`s) are signalled as per the underlying API: via a `TransactionCanceledException`.
283+
Use `Transaction.TransactionCanceledConditionalCheckFailed` to trap such conditions:
283284

284285
```fsharp
285286
try do! transaction.TransactWriteItems()
286287
return Some result
287-
with TransactWriteItemsRequest.TransactionCanceledConditionalCheckFailed -> return None
288+
with Transaction.TransactionCanceledConditionalCheckFailed -> return None
288289
```
289290

290-
See [`TransactWriteItems tests`](./tests/FSharp.AWS.DynamoDB.Tests/SimpleTableOperationTests.fs#130) for more details and examples.
291+
See [`TransactWriteItems tests`](./tests/FSharp.AWS.DynamoDB.Tests/SimpleTableOperationTests.fs#156) for more details and examples.
291292

292293
It generally costs [double or more the Write Capacity Units charges compared to using precondition expressions](https://zaccharles.medium.com/calculating-a-dynamodb-items-size-and-consumed-capacity-d1728942eb7c)
293294
on individual operations.

RELEASE_NOTES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
### 0.12.2-beta
2+
* (breaking) Revised multi-table transaction API (thanks @bartelink)
3+
14
### 0.12.1-beta
25
* Added support for `defaultArg` in update expressions on the same attribute, allowing SET if_not_exists semantics (eg { record with OptionalValue = Some (defaultArg record.OptionalValue "Default") })
36
* Allow empty strings in non-key attributes (thanks @purkhusid)

src/FSharp.AWS.DynamoDB/TableContext.fs

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,22 +1318,17 @@ type TableContext<'TRecord>
13181318
else
13191319
t.VerifyTableAsync()
13201320

1321-
member t.Transaction() =
1322-
match metricsCollector with
1323-
| Some metricsCollector -> Transaction(metricsCollector = metricsCollector)
1324-
| None -> Transaction()
1321+
/// <summary>Creates a new `Transaction`, using the DynamoDB client and metricsCollector configured for this `TableContext`</summary>
1322+
member _.CreateTransaction() =
1323+
Transaction(client, ?metricsCollector = metricsCollector)
13251324

13261325
/// <summary>
13271326
/// Represents a transactional set of operations to be applied atomically to a arbitrary number of DynamoDB tables.
13281327
/// </summary>
1328+
/// <param name="client">DynamoDB client instance</param>
13291329
/// <param name="metricsCollector">Function to receive request metrics.</param>
1330-
and Transaction(?metricsCollector: (RequestMetrics -> unit)) =
1330+
and Transaction(client: IAmazonDynamoDB, ?metricsCollector: (RequestMetrics -> unit)) =
13311331
let transactionItems = ResizeArray<TransactWriteItem>()
1332-
let mutable (dynamoDbClient: IAmazonDynamoDB) = null
1333-
1334-
let setClient client =
1335-
if dynamoDbClient = null then
1336-
dynamoDbClient <- client
13371332

13381333
let reportMetrics collector (tableName: string) (operation: Operation) (consumedCapacity: ConsumedCapacity list) (itemCount: int) =
13391334
collector
@@ -1353,35 +1348,30 @@ and Transaction(?metricsCollector: (RequestMetrics -> unit)) =
13531348
/// <param name="tableContext">Table context to operate on.</param>
13541349
/// <param name="item">Item to be put.</param>
13551350
/// <param name="precondition">Optional precondition expression.</param>
1356-
member this.Put<'TRecord>
1351+
member _.Put<'TRecord>
13571352
(
13581353
tableContext: TableContext<'TRecord>,
13591354
item: 'TRecord,
13601355
?precondition: ConditionExpression<'TRecord>
1361-
) : Transaction =
1362-
setClient tableContext.Client
1356+
) =
13631357
let req = Put(TableName = tableContext.TableName, Item = tableContext.Template.ToAttributeValues item)
13641358
precondition
13651359
|> Option.iter (fun cond ->
13661360
let writer = AttributeWriter(req.ExpressionAttributeNames, req.ExpressionAttributeValues)
13671361
req.ConditionExpression <- cond.Conditional.Write writer)
13681362
transactionItems.Add(TransactWriteItem(Put = req))
1369-
this
13701363

13711364
/// <summary>
13721365
/// Adds a ConditionCheck operation to the transaction.
13731366
/// </summary>
13741367
/// <param name="tableContext">Table context to operate on.</param>
13751368
/// <param name="key">Key of item to check.</param>
13761369
/// <param name="condition">Condition to check.</param>
1377-
member this.Check(tableContext: TableContext<'TRecord>, key: TableKey, condition: ConditionExpression<'TRecord>) : Transaction =
1378-
setClient tableContext.Client
1379-
1370+
member _.Check(tableContext: TableContext<'TRecord>, key: TableKey, condition: ConditionExpression<'TRecord>) =
13801371
let req = ConditionCheck(TableName = tableContext.TableName, Key = tableContext.Template.ToAttributeValues key)
13811372
let writer = AttributeWriter(req.ExpressionAttributeNames, req.ExpressionAttributeValues)
13821373
req.ConditionExpression <- condition.Conditional.Write writer
13831374
transactionItems.Add(TransactWriteItem(ConditionCheck = req))
1384-
this
13851375

13861376
/// <summary>
13871377
/// Adds an Update operation to the transaction.
@@ -1390,44 +1380,38 @@ and Transaction(?metricsCollector: (RequestMetrics -> unit)) =
13901380
/// <param name="key">Key of item to update.</param>
13911381
/// <param name="updater">Update expression.</param>
13921382
/// <param name="precondition">Optional precondition expression.</param>
1393-
member this.Update
1383+
member _.Update
13941384
(
13951385
tableContext: TableContext<'TRecord>,
13961386
key: TableKey,
13971387
updater: UpdateExpression<'TRecord>,
13981388
?precondition: ConditionExpression<'TRecord>
13991389

1400-
) : Transaction =
1401-
setClient tableContext.Client
1402-
1390+
) =
14031391
let req = Update(TableName = tableContext.TableName, Key = tableContext.Template.ToAttributeValues key)
14041392
let writer = AttributeWriter(req.ExpressionAttributeNames, req.ExpressionAttributeValues)
14051393
req.UpdateExpression <- updater.UpdateOps.Write(writer)
14061394
precondition |> Option.iter (fun cond -> req.ConditionExpression <- cond.Conditional.Write writer)
14071395
transactionItems.Add(TransactWriteItem(Update = req))
1408-
this
14091396

14101397
/// <summary>
14111398
/// Adds a Delete operation to the transaction.
14121399
/// </summary>
14131400
/// <param name="tableContext">Table context to operate on.</param>
14141401
/// <param name="key">Key of item to delete.</param>
14151402
/// <param name="precondition">Optional precondition expression.</param>
1416-
member this.Delete
1403+
member _.Delete
14171404
(
14181405
tableContext: TableContext<'TRecord>,
14191406
key: TableKey,
1420-
precondition: option<ConditionExpression<'TRecord>>
1421-
) : Transaction =
1422-
setClient tableContext.Client
1423-
1407+
?precondition: ConditionExpression<'TRecord>
1408+
) =
14241409
let req = Delete(TableName = tableContext.TableName, Key = tableContext.Template.ToAttributeValues key)
14251410
precondition
14261411
|> Option.iter (fun cond ->
14271412
let writer = AttributeWriter(req.ExpressionAttributeNames, req.ExpressionAttributeValues)
14281413
req.ConditionExpression <- cond.Conditional.Write writer)
14291414
transactionItems.Add(TransactWriteItem(Delete = req))
1430-
this
14311415

14321416
/// <summary>
14331417
/// Atomically applies a set of 1-100 operations to the table.<br/>
@@ -1436,13 +1420,13 @@ and Transaction(?metricsCollector: (RequestMetrics -> unit)) =
14361420
/// </summary>
14371421
/// <param name="clientRequestToken">The <c>ClientRequestToken</c> to supply as an idempotency key (10 minute window).</param>
14381422
member _.TransactWriteItems(?clientRequestToken) : Async<unit> = async {
1439-
if (Seq.length transactionItems) = 0 || (Seq.length transactionItems) > 100 then
1423+
if transactionItems.Count = 0 || transactionItems.Count > 100 then
14401424
raise
14411425
<| System.ArgumentOutOfRangeException(nameof transactionItems, "must be between 1 and 100 items.")
1442-
let req = TransactWriteItemsRequest(ReturnConsumedCapacity = returnConsumedCapacity, TransactItems = (ResizeArray transactionItems))
1426+
let req = TransactWriteItemsRequest(ReturnConsumedCapacity = returnConsumedCapacity, TransactItems = transactionItems)
14431427
clientRequestToken |> Option.iter (fun x -> req.ClientRequestToken <- x)
14441428
let! ct = Async.CancellationToken
1445-
let! response = dynamoDbClient.TransactWriteItemsAsync(req, ct) |> Async.AwaitTaskCorrect
1429+
let! response = client.TransactWriteItemsAsync(req, ct) |> Async.AwaitTaskCorrect
14461430
maybeReport
14471431
|> Option.iter (fun r ->
14481432
response.ConsumedCapacity
@@ -2150,8 +2134,8 @@ module Scripting =
21502134
let spec = Throughput.Provisioned provisionedThroughput
21512135
t.UpdateTableIfRequiredAsync(spec) |> Async.Ignore |> Async.RunSynchronously
21522136

2153-
/// Helpers for working with <c>TransactWriteItemsRequest</c>
2154-
module TransactWriteItemsRequest =
2137+
/// Helpers for working with <c>Transaction</c>
2138+
module Transaction =
21552139
/// <summary>Exception filter to identify whether a <c>TransactWriteItems</c> call has failed due to
21562140
/// one or more of the supplied <c>precondition</c> checks failing.</summary>
21572141
let (|TransactionCanceledConditionalCheckFailed|_|): exn -> unit option =

tests/FSharp.AWS.DynamoDB.Tests/MetricsCollectorTests.fs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,9 @@ type Tests(fixture: TableFixture) =
106106
collector.Clear()
107107

108108
let item = mkItem (guid ()) (guid ()) 0
109-
do!
110-
Transaction(collector.Collect)
111-
.Put(sut, item, compile <@ fun t -> NOT_EXISTS t.RangeKey @>)
112-
.TransactWriteItems()
109+
let transaction = Transaction(sut.Client, collector.Collect)
110+
transaction.Put(sut, item, compile <@ fun t -> NOT_EXISTS t.RangeKey @>)
111+
do! transaction.TransactWriteItems()
113112

114113
test
115114
<@
@@ -131,14 +130,14 @@ type Tests(fixture: TableFixture) =
131130
let sut = rawTable.WithMetricsCollector(collector.Collect)
132131

133132
let item = mkItem (guid ()) (guid ()) 0
133+
let transaction = rawTable.CreateTransaction()
134+
transaction.Put(sut, item, compile <@ fun t -> EXISTS t.RangeKey @>)
134135
let mutable failed = false
135136
try
136137
do!
137138
// The check will fail, which triggers a throw from the underlying AWS SDK; there's no way to extract the consumption info in that case
138-
Transaction()
139-
.Put(sut, item, compile <@ fun t -> EXISTS t.RangeKey @>)
140-
.TransactWriteItems()
141-
with TransactWriteItemsRequest.TransactionCanceledConditionalCheckFailed ->
139+
transaction.TransactWriteItems()
140+
with Transaction.TransactionCanceledConditionalCheckFailed ->
142141
failed <- true
143142
true =! failed
144143
[] =! collector.Metrics

tests/FSharp.AWS.DynamoDB.Tests/SimpleTableOperationTests.fs

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,9 @@ type ``TransactWriteItems tests``(table1: TableFixture, table2: TableFixture) =
182182
[<Fact>]
183183
let ``Minimal happy path`` () = async {
184184
let item = mkItem ()
185-
do!
186-
Transaction()
187-
.Put(table1, item, doesntExistConditionTable1)
188-
.TransactWriteItems()
185+
let transaction = table1.CreateTransaction()
186+
transaction.Put(table1, item, doesntExistConditionTable1)
187+
do! transaction.TransactWriteItems()
189188

190189
let! itemFound = table1.ContainsKeyAsync(table1.Template.ExtractKey item)
191190
true =! itemFound
@@ -196,11 +195,10 @@ type ``TransactWriteItems tests``(table1: TableFixture, table2: TableFixture) =
196195
let item = mkItem ()
197196
let compatibleItem = mkCompatibleItem ()
198197

199-
do!
200-
Transaction()
201-
.Put(table1, item, doesntExistConditionTable1)
202-
.Put(table2, compatibleItem, doesntExistConditionTable2)
203-
.TransactWriteItems()
198+
let transaction = table1.CreateTransaction()
199+
transaction.Put(table1, item, doesntExistConditionTable1)
200+
transaction.Put(table2, compatibleItem, doesntExistConditionTable2)
201+
do! transaction.TransactWriteItems()
204202

205203
let! itemFound = table1.ContainsKeyAsync(table1.Template.ExtractKey item)
206204
true =! itemFound
@@ -213,13 +211,12 @@ type ``TransactWriteItems tests``(table1: TableFixture, table2: TableFixture) =
213211
let ``Minimal Canceled path`` () = async {
214212
let item = mkItem ()
215213

214+
let transaction = table1.CreateTransaction()
215+
transaction.Put(table1, item, existsConditionTable1)
216216
let mutable failed = false
217217
try
218-
do!
219-
Transaction()
220-
.Put(table1, item, existsConditionTable1)
221-
.TransactWriteItems()
222-
with TransactWriteItemsRequest.TransactionCanceledConditionalCheckFailed ->
218+
do! transaction.TransactWriteItems()
219+
with Transaction.TransactionCanceledConditionalCheckFailed ->
223220
failed <- true
224221

225222
true =! failed
@@ -233,18 +230,17 @@ type ``TransactWriteItems tests``(table1: TableFixture, table2: TableFixture) =
233230
let item, item2 = mkItem (), mkItem ()
234231
let! key = table1.PutItemAsync item
235232

236-
let transaction =
237-
if shouldFail then
238-
Transaction().Check(table1, key, doesntExistConditionTable1)
239-
else
240-
Transaction()
241-
.Check(table1, key, existsConditionTable1)
242-
.Put(table1, item2)
233+
let transaction = table1.CreateTransaction()
234+
if shouldFail then
235+
transaction.Check(table1, key, doesntExistConditionTable1)
236+
else
237+
transaction.Check(table1, key, existsConditionTable1)
238+
transaction.Put(table1, item2)
243239

244240
let mutable failed = false
245241
try
246242
do! transaction.TransactWriteItems()
247-
with TransactWriteItemsRequest.TransactionCanceledConditionalCheckFailed ->
243+
with Transaction.TransactionCanceledConditionalCheckFailed ->
248244
failed <- true
249245

250246
failed =! shouldFail
@@ -257,14 +253,14 @@ type ``TransactWriteItems tests``(table1: TableFixture, table2: TableFixture) =
257253
let ``All paths`` shouldFail = async {
258254
let item, item2, item3, item4, item5, item6, item7 = mkItem (), mkItem (), mkItem (), mkItem (), mkItem (), mkItem (), mkItem ()
259255
let! key = table1.PutItemAsync item
260-
let transaction = Transaction()
256+
let transaction = table1.CreateTransaction()
261257

262258
let requests =
263259
[ transaction.Update(table1, key, compileUpdateTable1 <@ fun t -> { t with Value = 42 } @>, existsConditionTable1)
264260
transaction.Put(table1, item2)
265261
transaction.Put(table1, item3, doesntExistConditionTable1)
266-
transaction.Delete(table1, table1.Template.ExtractKey item4, Some doesntExistConditionTable1)
267-
transaction.Delete(table1, table1.Template.ExtractKey item5, None)
262+
transaction.Delete(table1, table1.Template.ExtractKey item4, doesntExistConditionTable1)
263+
transaction.Delete(table1, table1.Template.ExtractKey item5)
268264
transaction.Check(
269265
table1,
270266
table1.Template.ExtractKey item6,
@@ -281,7 +277,7 @@ type ``TransactWriteItems tests``(table1: TableFixture, table2: TableFixture) =
281277
let mutable failed = false
282278
try
283279
do! transaction.TransactWriteItems()
284-
with TransactWriteItemsRequest.TransactionCanceledConditionalCheckFailed ->
280+
with Transaction.TransactionCanceledConditionalCheckFailed ->
285281
failed <- true
286282
failed =! shouldFail
287283

@@ -310,13 +306,12 @@ type ``TransactWriteItems tests``(table1: TableFixture, table2: TableFixture) =
310306

311307
[<Fact>]
312308
let ``Empty request list is rejected with AORE`` () =
313-
shouldBeRejectedWithArgumentOutOfRangeException (Transaction())
309+
shouldBeRejectedWithArgumentOutOfRangeException (Transaction(table1.Client))
314310
|> Async.RunSynchronously
315-
|> ignore
316311

317312
[<Fact>]
318313
let ``Over 100 writes are rejected with AORE`` () =
319-
let Transaction = Transaction()
314+
let Transaction = Transaction(table1.Client)
320315
for _x in 1..101 do
321316
Transaction.Put(table1, mkItem ()) |> ignore
322317

0 commit comments

Comments
 (0)