Created
October 19, 2015 16:29
-
-
Save rmmeans/df51793f72ed844dff21 to your computer and use it in GitHub Desktop.
Play Framework DynamoDB Json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: .... | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.