Asynchronous Scala Clients for Amazon Web Services
A model. A forum has a name and category, and counts of the number of threads, messages, and views.
import com.github.dwhjames.awswrap.dynamodb._
case class Forum(
name: String,
category: String,
threads: Long,
messages: Long,
views: Long
)In the companion object we’ll track information about the corresponding DynamoDB table. First, we statically store the table name, the attribute names, and the table creation request object.
object Forum {
val tableName = "Forum"
object Attributes {
val name = "Name"
val category = "Category"
val threads = "Threads"
val messages = "Messages"
val views = "Views"
}
val tableRequest =
new CreateTableRequest()
.withTableName(Forum.tableName)
.withProvisionedThroughput(
Schema.provisionedThroughput(10L, 5L))
.withAttributeDefinitions(
Schema.stringAttribute(Attributes.name))
.withKeySchema(
Schema.hashKey(Attributes.name))Second, we define an implicit serializer
that will convert between our Forum type and a mapping of attribute names as
String and values as AttributeValue.
implicit object forumSerializer
extends DynamoDBSerializer[Forum] {
override val tableName = Forum.tableName
override val hashAttributeName = Attributes.name
override def primaryKeyOf(forum: Forum) =
Map(Attributes.name -> forum.name)
override def toAttributeMap(forum: Forum) =
Map(
Attributes.name -> forum.name,
Attributes.category -> forum.category,
Attributes.threads -> forum.threads,
Attributes.messages -> forum.messages,
Attributes.views -> forum.views
)
override def fromAttributeMap(
item: collection.mutable.Map[String, AttributeValue]) =
Forum(
name = item(Attributes.name),
category = item(Attributes.category),
threads = item(Attributes.threads),
messages = item(Attributes.messages),
views = item(Attributes.views)
)
}
}The dynamodb package object
contains implicit views between Scala types and AttributeValue, which makes
the definitions of toAttributeMap and fromAttributeMap clean (and maybe a
little magical).
Let’s construct a couple of sample forum objects.
val sampleForums = Seq(
Forum(
name = "Amazon DynamoDB",
category = "Amazon Web Services",
threads = 2,
messages = 3,
views = 1000
),
Forum(
name = "Amazon S3",
category = "Amazon Web Services",
threads = 1,
messages = 1,
views = 500
)
)Now let’s create a client to access DynamoDB: the raw SDK client, and our wrapping client.
val sdkClient = new AmazonDynamoDBAsyncClient(myCredentials)
val client = new AmazonDynamoDBScalaClient(sdkClient)And then in turn wrap that client in the mapper.
val mapper = AmazonDynamoDBScalaMapper(client)Finally, assuming we have a live table, we can demonstrate the mapper with a few example operations.
Await.result(
for {We can initialize the table by loading all our sample objects as items into the table, using batchDump.
_ <- mapper.batchDump(sampleForums)We can count the number of items in the table, using countScan.
forumCount <- mapper.countScan[Forum]()We can scan for items in the table, using scan with a condition.
forums <- mapper.scan[Forum](
Map("Category" ->
ScanCondition.contains("Services")))And we can find a forum item in the table by its hash key, using loadByKey.
Some(forum) <- mapper.loadByKey[Forum](sampleForums.head.name)
} yield {
assert {
forumCount == sampleForums.size
}
assert {
forums.size == sampleForums.size
}
assert {
forum == sampleForums.head
}
},
10.seconds
)… and checking that these return the expected results.