fsprojects / FSharp.AWS.DynamoDB

F# wrapper API for AWS DynamoDB

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Handling of ResourceNotFoundException

NickDarvey opened this issue · comments

I notice that UpdateItemAsync doesn't handle 400: ResourceNotFoundExceptions and subsequently returns a 'TRecord, not a 'TRecord option. I have a scenario where I want to use an UpdateIfExists kind of function so I can achieve something in one request, not two. In my case, it wouldn't be particularly exceptional that it sometimes doesn't exist.

Is there a design reason why handling of ResourceNotFoundException isn't part of this library that I should be aware of? Why I might want to avoid that approach? (I'm new to DynamoDB...)

member __.UpdateItemAsync(key : TableKey, updater : UpdateExpression<'TRecord>,
?precondition : ConditionExpression<'TRecord>, ?returnLatest : bool) : Async<'TRecord> = async {
let kav = template.ToAttributeValues(key)
let request = new UpdateItemRequest(Key = kav, TableName = tableName)
request.ReturnValues <-
if defaultArg returnLatest true then ReturnValue.ALL_NEW
else ReturnValue.ALL_OLD
let writer = new AttributeWriter(request.ExpressionAttributeNames, request.ExpressionAttributeValues)
request.UpdateExpression <- updater.UpdateOps.Write(writer)
match precondition with
| Some pc -> request.ConditionExpression <- pc.Conditional.Write writer
| _ -> ()
let! ct = Async.CancellationToken
let! response = client.UpdateItemAsync(request, ct) |> Async.AwaitTaskCorrect
if response.HttpStatusCode <> HttpStatusCode.OK then
failwithf "PutItem request returned error %O" response.HttpStatusCode
return template.OfAttributeValues response.Attributes
}

TL;DR thats because the semantics of UpdateItem are such that it is valid for it to work if the item is not present (no, the tests dont convey that)

Edits an existing item's attributes, or adds a new item to the table if it does not already exist. You can put, delete, or add attribute values. You can also perform a conditional update on an existing item (insert a new attribute name-value pair if it doesn't exist, or replace an existing name-value pair if it has certain expected attribute values).

doesn't handle 400: ResourceNotFoundException

The GetItem API handles item not found by having the response have response.IsItemSet = false. ResourceNotFoundException is about the table or indices etc being missing

That sort of thing is covered by the default TableContext.Create having verifyTable = true and/or createIfNotExists = true at some stage
See cleanup work to clarify those semantics in #43


Your choices are:

  • conditional put (can't look at existing state, can predicate whether anything happens via the conditional check)
  • conditional update (can map from existing state, can predicate whether anything happens via conditional check)

The AWS SDK's ReturnValues controls emission of results where there has been a successful outcome.

A condition check failure is handled as an Exception, there is no other way, and you do not get to see the original state if it fails

If you do TransactWriteItems (which is not handled by TableRecord atm), you can get it to emit the input Item that triggered a condition check failure, but it does not really confer much power beyond the power of UpdateItem or PutItem (and costs twice as much) (aside from the key feature of making two operations on one or more tables be atomic of course)

A TryGetItem has been added here recently

Sounds like what you're after is either an optimistic UpdateItem guarded by an exists precondition, or something like:

match! t.TryGet key with
| Some v -> // conditional Put or UpdateItem
| None -> // conditional Put

(Or simply use Equinox.DynamoStore, which will let you (unit test and) write a decide function based on folding of events without worrying about all this sort of minutiae :D)

Let me know if you have follow-up questions

Hm I note with interest that the ResourceNotFoundException that GetItemAsync and friends throws in this library is a pretty questionable design, which probably did not help matters in this instance (and also makes my explanation a little less clear than it could be - I did not have this in mind when I wrote it)

The xmldoc for ResourceNotFoundException says:

/// The operation tried to access a nonexistent table or index. The resource might not
/// be specified correctly, or its status might not be <code>ACTIVE</code>.

Changing the exception would obviously be a breaking change that's hard to argue for at this stage (the real fix is to use TryGetItem)
Added some comments re this in #46

Thanks @bartelink, that does clarify.

(Or simply use Equinox.DynamoStore, which will let you (unit test and) write a decide function based on folding of events without worrying about all this sort of minutiae :D)

This is a nice surprise too!