# Basic queries
D4S has a rich support of almost all DynamoDB operations. We will start with the most basic ones:
- getItem - retrieves a single item from a table.
- putItem - adds a new item or replaces existing one.
- updateItem - updates an existing item.
- deleteItem - removes an item from a table.
- scan - fetches all elements in a table.
- query - fetches all elements of a table that match some criteria using hash/range keys.
To run any query that built using D4S you must use DynamoConnector
. Say we have a variable named connector
of DynamoConnector
type,
then typical call to the database would look like this:
connector.run("query name") {
// query itself.
}
Remember our example with leaderboard service? Let's implement Ladder
interface and see how we could use one of the listed above queries
to interact with DB. If you forget how Ladder
interface looks like, here is a quick reminder:
trait Ladder[F[_, _]] {
def submitScore(userId: UserId, score: Score): F[QueryFailure, Unit]
def getScores: F[QueryFailure, List[UserWithScore]]
}
Typical implementation of the persistence layer using D4S could look like this:
final class D4SLadder[F[+_, +_]: Bifunctor2](connector: DynamoConnector[F], ladderTable: LadderTable) extends Ladder[F] {
import ladderTable._
override def getScores: F[QueryFailure, List[UserWithScore]] = {
connector
.run("get scores query") {
table.scan.decodeItems[UserIdWithScoreStored].execPagedFlatten()
}
.leftMap(err => QueryFailure(err.message, err.cause))
.map(_.map(_.toAPI))
}
override def submitScore(userId: UserId, score: Score): F[QueryFailure, Unit] = {
connector
.run("submit user's score") {
table.updateItem(UserIdWithScoreStored(userId.value, score.value))
}.leftMap(err => QueryFailure(err.message, err.cause)).void
}
}
A lot of things happening here, but don't worry we'll explain everything in a bit. D4SLadder
constructor requires two parameters: DynamoConnector
to run a query and LadderTable
which is our table definition. We also require and instance of Bifunctor2
from Izumi for leftMap
.
# Scan and Query
In order to retrieve scores, we need to scan the whole ladder table. All queries are implemented as extension methods for TableReferece
data type.
So to build a query you need to use table
value from LadderTable
as we described here:
table.scan.decodeItems[UserIdWithScoreStored].execPagedFlatten()
This query simply scans the table and decodes items (using previously defined codecs). Okay, but why we need this .execPagedFlatten()
combinator?
We could have a huge number of records in the table that couldn't fit in one page of scan result. Using execPagedFlatten
we create a query
that handles pagination and flattens all pages into a single one-dimensional list of items. What if we'll change our interface and add one more method
to fetch records with users that have a score greater or equal to 42. This is how such a query could be expressed with D4S:
import d4s.implicits._
table
.query(mainFullKey(userId))
.withFilterExpression("score".of[Long] >= 42)
.decodeItems[UserIdWithScoreStored]
.execPagedFlatten()
Here we use query
operation that requires at least one table's key, we pass the user's id. You've already known about decodeItems
and
execPagedFlatten
, but this withFilterExpression
combinator is something new. This combinator applies a filter on a query.
The filter requires a Condition
which could be build using implicit methods from d4s.implicits
object. The of
method specify type of the attribute
we wanna use in an expression. In our case we use score attribute and tell D4S that it has type of Long, then using method >=
compare it with a particular value.
For more information about conditionals, please refer to Conditionals page.
# Put, Update and Delete
Now, let's look at submitScore
method. The best way to put data or update it if it's already in the table using updateItem
query.
All you need is to pass the data you want to star into updateItem
combinator.
table.updateItem(UserIdWithScoreStored(userId.value, score.value))
Put operation won't differ from update too much:
table.putItem(UserIdWithScoreStored(userId.value, score.value))
And the last, but not least is delete operation. D4S has a deleteItem
method that takes a table's key and removes and item.
Let's use it to remove a particular score from the ladder:
table.deleteItem(mainFullKey(userId))
Okay, in this section we finally discovered how to make queries with D4S and before we go to know for other operation and possibilities that D4S provides, we would like to highlight what we've learnt so far:
- we able to setup project with D4S
- we able to define a table
- we able to perform basic operations on DynamoDB.
Good job, and see ya in the next chapter!