Skip to content

Instantly share code, notes, and snippets.

@rmmeans
Created October 19, 2015 16:29
Show Gist options
  • Save rmmeans/df51793f72ed844dff21 to your computer and use it in GitHub Desktop.
Save rmmeans/df51793f72ed844dff21 to your computer and use it in GitHub Desktop.
Play Framework DynamoDB Json
object DynamoReader {
def typeReader[A](f: (JsObject => JsResult[A])) = new Reads[A] {
def reads(json: JsValue): JsResult[A] = json match {
case obj: JsObject => f(obj)
case _ => JsError(Seq(JsPath() -> Seq(ValidationError("error.expected.jsobject"))))
}
}
}
object DynamoString {
val reads: Reads[String] = DynamoReader.typeReader[String](x => (x \ "S").validate[String])
val writes = new Writes[String] {
override def writes(o: String): JsValue = Json.obj("S" -> o)
}
}
object DynamoNumber {
val longReads: Reads[Long] = DynamoReader.typeReader[Long](x => (x \ "N").validate[String].map(_.toLong))
val longWrites = new Writes[Long] {
override def writes(o: Long): JsValue = Json.obj("N" -> JsString(o.toString))
}
}
object DynamoBoolean {
val reads: Reads[Boolean] = DynamoReader.typeReader[Boolean](x => (x \ "BOOL").validate[Boolean])
val writes = new Writes[Boolean] {
override def writes(o: Boolean): JsValue = Json.obj("BOOL" -> o)
}
}
case class Sample(someString: String, someLong: Long, optionalBoolean: Option[Boolean])
object Sample {
implicit val reads: Reads[Sample] = (
(JsPath \ "some_string").read[String](DynamoString.reads) and
(JsPath \ "some_long").read[Long](DynamoNumber.longReads) and
(JsPath \ "optional_bool").readNullable[Boolean](DynamoBoolean.reads)
)(Sample.apply _)
implicit val writes: Writes[Sample] = (
(JsPath \ "some_string").write[String](DynamoString.writes) and
(JsPath \ "some_long").write[Long](DynamoNumber.longWrites) and
(JsPath \ "optional_bool").writeNullable[Boolean](DynamoBoolean.writes)
)(unlift(Sample.unapply))
}
class DynamoDBWSSample(credProvider: AWSCredentialsProvider, ws: WSAPI, config: Configuration) {
val conf = config.underlying
val serviceUrl = conf.getString("dynamoDB.url")
val serviceName = conf.getString("dynamoDB.serviceName")
val serviceRegion = conf.getString("dynamoDB.serviceRegion")
val awsPlayWSSigner = (wsReq: WSRequest, signer: Aws4Signer) => AwsRequestHolder(wsReq, signer).execute()
//Must be a function so that instances of the class don't cache the credentials from the provider.
val awsSigner = (credProvider: AWSCredentialsProvider) => {
val credentials = credProvider.getCredentials
new Aws4Signer(AwsCredentials(credentials.getAWSAccessKeyId, credentials.getAWSSecretKey), serviceName, serviceRegion)
}
val dynamoWSSigner = (wsReq: WSRequest) => awsPlayWSSigner(wsReq, awsSigner(credProvider))
val dynamoWSRequest = ws.url(serviceUrl).withHeaders("Content-Type" -> "application/x-amz-json-1.0").withMethod("POST")
val dynamoPutItem = (data: JsObject) => dynamoWSSigner(dynamoWSRequest.withHeaders("x-amz-target" -> "DynamoDB_20120810.PutItem").withBody(data))
val dynamoGetItem = (data: JsObject) => dynamoWSSigner(dynamoWSRequest.withHeaders("x-amz-target" -> "DynamoDB_20120810.GetItem").withBody(data))
val dynamoDeleteItem = (data: JsObject) => dynamoWSSigner(dynamoWSRequest.withHeaders("x-amz-target" -> "DynamoDB_20120810.DeleteItem").withBody(data))
val dynamoQueryItem = (data: JsObject) => dynamoWSSigner(dynamoWSRequest.withHeaders("x-amz-target" -> "DynamoDB_20120810.Query").withBody(data))
//Once the DynamoFunction Helpers are created, executing signed dynamo requests is not much harder than using the SDK itself...
def findSample(someString: String): Future[Option[Sample]] = {
val req = Json.obj(
"TableName" -> "sample.table.name",
"Key" -> Json.obj("some_string" -> Json.obj("S" -> someString)),
"ReturnConsumedCapacity" -> "NONE",
"ConsistentRead" -> true
)
dynamoGetItem(req).map { response =>
if (response.status == 200) {
(response.json \ "Item").as[JsObject].validate[Sample].asOpt
} else {
//TODO: ....
}
}
}
}
@rmmeans
Copy link
Author

rmmeans commented Oct 19, 2015

The goal of this gist is to show how one might integrate with DynamoDB using the WS library and this library to sign the requests: https://github.com/Kaliber/play-s3/tree/master/src/main/scala/fly/play/aws

I've provided a couple of read / writers for the different dynamo types. This list is by no means complete - but it shows you how you can handle the different dynamo types while still using Play's built in read / write converters into scala case classes with ease.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment