Clojure and DynamoDB with Faraday, Part 4

Update and friends

This is the final part of the series where I convert Amazon’s Getting Started to Clojure with Faraday. If you’ve just arrived, the previous parts were:

This part will correspond to steps 7 and 8 of the Getting Started guide. Let’s move on to updating and deleting data!

Update an item

Up date allows us to modify an item’s attributes, add new ones, or remove existing attributes. We’ll use update expressions, which have the same restrictions as the expressions we saw on part 2.

We also can specify which values we want returned after the update:

  • :all-old returns all attribute values as they appeared before the update
  • :updated-old returns only the updated attributes as they appeared before the update
  • :all-new returns all attribute values as they appear after the update
  • :updated-new returns only the updated attributes, as they appear after the update

First we’ll add a few new attributes to our existing :music table:

1
2
3
4
5
(far/update-item client-opts :music {:artist "No One You Know"
:song-title "Call Me Today"}
{:update-expr "SET label = :label"
:expr-attr-vals {":label" "Global Records"}
:return :all-new})

Since we requested :all-new, we’ll get the entire item back.

1
2
3
4
5
6
7
8
{:artist "No One You Know",
:year 2015N,
:price 2.14M,
:genre "country",
:song-title "Call Me Today",
:label "Global Records",
:album-title "Somewhat Famous",
:tags {:composers ["Smith" "Jones" "Davis"], :length-in-seconds 214N}}

But we’re not limited to one update per expression - we can (for example) both set a price and remove an element using path notation.

1
2
3
4
5
(far/update-item client-opts :music {:artist "No One You Know"
:song-title "Call Me Today"}
{:update-expr "SET price = :price REMOVE tags.composers[2]"
:expr-attr-vals {":price" 0.89}
:return :all-new})

So long, Mr. Davis.

1
2
3
4
5
6
7
8
{:artist "No One You Know",
:year 2015N,
:price 0.89M,
:genre "country",
:label "Global Records",
:song-title "Call Me Today",
:album-title "Somewhat Famous",
:tags {:composers ["Smith" "Jones"], :length-in-seconds 214N}}

Specify a conditional write

Much like we added a conditional expression to put-item back on part 1, we can also send a condition expression to our update-item call.

1
2
3
4
5
6
(far/update-item client-opts :music {:artist "No One You Know"
:song-title "Call Me Today"}
{:update-expr "SET label = :label"
:expr-attr-vals {":label" "New Wave Recordings"}
:cond-expr "attribute_not_exists(label)"
:return :all-new})

Which will get us a ConditionalCheckFailedException since the attribute “label” - which we specified on :cond-expr shouldn’t exist - is already there.

Specify an atomic counter

We’ll now kill two birds with one stone: we’ll demo how to do an atomic counter, and retrieve only the updated attributes for the item.

Let’s first set a new plays attribute to zero.

1
2
3
4
5
6
7
8
(far/update-item client-opts :music {:artist "No One You Know"
:song-title "Call Me Today"}
{:update-expr "SET plays = :val"
:expr-attr-vals {":val" 0}
:return :updated-new})
;; =>
{:plays 0N}

We can now use a function on update-item‘s update expression to increase it.

1
2
3
4
5
6
7
8
(far/update-item client-opts :music {:artist "No One You Know"
:song-title "Call Me Today"}
{:update-expr "SET plays = plays + :incr"
:expr-attr-vals {":incr" 1}
:return :updated-new})
;; =>
{:plays 1N}

If we execute that same call multiple times, the item is updated as expected.

1
2
3
4
5
6
7
8
;; =>
{:plays 2N}
;; =>
{:plays 3N}
;; =>
{:plays 4N}

Delete an item

We can also permanently delete an item using its full hash key.

1
2
3
4
5
(far/delete-item client-opts :music {:artist "No One You Know"
:song-title "Call Me Today"})
;; =>
nil

And poof! It’s gone, forever.

Specify a conditional delete

You won’t be surprised to find that deletes also support conditional expressions. We can add a check that, if it goes unsatisfied, will let us know by raising an exception.

1
2
3
4
(far/delete-item client-opts :music {:artist "No One You Know"
:song-title "My Dog Spot"}
{:cond-expr "price = :price"
:expr-attr-vals {":price" 0}})

Which, as before, gets us a ConditionalCheckFailedException because the item does not have the expected price.

Deleting a table

And finally, we can delete a table on its entirety.

1
(far/delete-table client-opts :music)

This will return the table’s last status.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{:lsindexes nil,
:gsindexes [{:name :genre-and-price-index,
:size 517,
:item-count 3,
:key-schema [{:name :genre, :type :hash} {:name :price, :type :range}],
:projection {:projection-type "ALL", :non-key-attributes nil},
:throughput {:read 1, :write 1, :last-decrease nil,
:last-increase nil, :num-decreases-today nil}}],
:name :music,
:throughput {:read 1,
:write 1,
:last-decrease #inst"1970-01-01T00:00:00.000-00:00",
:last-increase #inst"1970-01-01T00:00:00.000-00:00",
:num-decreases-today 0},
:prim-keys {:artist {:key-type :hash, :data-type :s},
:song-title {:key-type :range, :data-type :s},
:price {:data-type :n},
:genre {:data-type :s}},
:size 517,
:status :active,
:item-count 4,
:creation-date #inst"2015-12-10T14:07:55.100-00:00",
:indexes nil}

And we’re done!

That’s it! As you can see, Faraday provides a significantly more succinct interface to DynamoDB than even Amazon’s unceremonious Javascript API. It’s a growing project, but a great companion to Amazon’s document store.

Author

...
Ricardo J. Méndez