Skip to content

Commit

Permalink
Improve : Use scala.concurrent.Future instead of custom callback func…
Browse files Browse the repository at this point in the history
…tion.

1. Now all asynchronous APIs return Future object. You can use it to return as Web response in Play!.
2. Wrap asynchronous functions with API.sync if you want to run APIs synchronously. See Application.sync.
  • Loading branch information
kangmo committed May 5, 2014
1 parent a5d92ec commit 99effea
Show file tree
Hide file tree
Showing 11 changed files with 485 additions and 280 deletions.
490 changes: 330 additions & 160 deletions app/controllers/Application.scala

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions app/views/index.scala.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
@(message: String)

@main("Welcome to Play") {

@play20.welcome(message)

<p>
<a href="@routes.Application.async">Run Asynchronous API Examples</a>
</p>
<p>
<a href="@routes.Application.sync">Run Synchronous API Examples</a>
</p>
}
2 changes: 2 additions & 0 deletions conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

# Home page
GET / controllers.Application.index
GET /sync controllers.Application.sync
GET /async controllers.Application.async

# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.at(path="/public", file)
8 changes: 7 additions & 1 deletion subprojs/tradeapi/app/api/API.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import org.kangmo.http._
import org.kangmo.helper._

import java.math.BigDecimal

import scala.concurrent.{Future,Await}
import scala.concurrent.duration._
case class Version (
major : Int,
minor : Int,
Expand All @@ -30,6 +31,11 @@ case class OAuthResponse(token_type: String, access_token: String, expires_in: L
object API {
val market = new MarketChannel()

// You can Wrap an API with this function to synchronously wait for the API call.
def sync[T](f : Future[T]) : T = {
Await.result(f, 60 seconds /*timeout*/ )
}

def version() : Version = {
val jsonResponse = HTTP.get(URLPrefix.prefix + s"version")
val versonObject = Json.deserialize[Version](jsonResponse)
Expand Down
2 changes: 1 addition & 1 deletion subprojs/tradeapi/app/api/APITypes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package org.kangmo.tradeapi

import java.math.BigDecimal

case class Error(status:String)
class APIException(status:String) extends Exception(status)

case class Price(currency: String, value: BigDecimal)
case class Amount(currency: String, value: BigDecimal)
Expand Down
44 changes: 44 additions & 0 deletions subprojs/tradeapi/app/api/AbstractChannel.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.kangmo.tradeapi

import org.kangmo.http._
import org.kangmo.helper._

import java.math.BigDecimal
import scala.concurrent._

abstract class AbstractChannel() {
def getPublicFuture[T : Manifest](resource : String) : Future[T] = {
val p = promise[T]

HTTPActor.dispatcher ! GetPublicResource(resource) { jsonResponse =>
val obj : T = Json.deserialize[T](jsonResponse)
p success obj
}

p.future
}
}

abstract class AbstractUserChannel(context : Context) {
def getUserFuture[T : Manifest](resource : String) : Future[T] = {
val p = promise[T]

HTTPActor.dispatcher ! GetUserResource(context, resource) { jsonResponse =>
val obj : T = Json.deserialize[T](jsonResponse)
p success obj
}

p.future
}

def postUserFuture[T : Manifest](resource : String, postData : String) : Future[T] = {
val p = promise[T]

HTTPActor.dispatcher ! PostUserResource(context, resource, postData) { jsonResponse =>
val obj : T = Json.deserialize[T](jsonResponse)
p success obj
}

p.future
}
}
43 changes: 28 additions & 15 deletions subprojs/tradeapi/app/api/CoinChannel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.kangmo.http._
import org.kangmo.helper._

import java.math.BigDecimal
import scala.concurrent._

case class CoinStatus(
timestamp : java.sql.Timestamp,
Expand All @@ -21,40 +22,52 @@ case class CoinOutRequest(currency: String, id : Long)
private case class CoinOutStatus(status: String, transferId: Long)
private case class AssignCoinAddressResponse(status : String, address : String)

class CoinChannel(context : Context) {
class CoinChannel(context : Context) extends AbstractUserChannel(context) {

def assignInAddress() = {
val p = promise[CoinAddress]

def assignInAddress()(callback : Either[Error, CoinAddress] => Unit ) {
val postData = "currency=btc"

HTTPActor.dispatcher ! PostUserResource(context, "user/coins/address/assign", postData ) { jsonResponse =>
val response = Json.deserialize[AssignCoinAddressResponse](jsonResponse)
val result = if (response.status == "success") Right( CoinAddress( response.address ) ) else Left( Error(response.status))
callback(result)
if (response.status == "success") p success CoinAddress( response.address )
else p failure new APIException(response.status)
}

p.future
}

def requestCoinOut(amount : Amount, address : CoinAddress)(callback : Either[Error, CoinOutRequest] => Unit ) {
def requestCoinOut(amount : Amount, address : CoinAddress) = {
val p = promise[CoinOutRequest]

val postData = s"currency=${amount.currency}&amount=${amount.value}&address=${address.address}"
HTTPActor.dispatcher ! PostUserResource(context, "user/coins/out", postData ) { jsonResponse =>
val response = Json.deserialize[CoinOutStatus](jsonResponse)
val result = if (response.status == "success") Right( CoinOutRequest( amount.currency, response.transferId ) ) else Left( Error(response.status))
callback(result)
if (response.status == "success") p success CoinOutRequest( amount.currency, response.transferId )
else p failure new APIException(response.status)
}

p.future
}

def queryCoinOut(request : Option[CoinOutRequest] = None)(callback : Either[Error, Seq[CoinStatus]] => Unit ) {
def queryCoinOut(request : Option[CoinOutRequest] = None) = {
val params = "currency=btc" + ( if (request == None) "" else s"&id=${request.get.id}" )
HTTPActor.dispatcher ! GetUserResource(context, s"user/coins/status?$params" ) { jsonResponse =>
val response = Json.deserialize[Seq[CoinStatus]](jsonResponse)
callback( Right( response ) )
}

getUserFuture[Seq[CoinStatus]](s"user/coins/status?$params")
}

def cancelCoinOut(request : CoinOutRequest)(callback : Option[Error] => Unit ) {
def cancelCoinOut(request : CoinOutRequest) = {
val p = promise[CoinOutRequest]

val postData = s"currency=${request.currency}&id=${request.id}"

HTTPActor.dispatcher ! PostUserResource(context, "user/coins/out/cancel", postData ) { jsonResponse =>
val response = Json.deserialize[CoinOutStatus](jsonResponse)
val result = if (response.status == "success") None else Some( Error(response.status))
callback(result)
if (response.status == "success") p success request
else p failure new APIException( response.status )
}

p.future
}
}
56 changes: 36 additions & 20 deletions subprojs/tradeapi/app/api/FiatChannel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.kangmo.http._
import org.kangmo.helper._

import java.math.BigDecimal
import scala.concurrent._

case class FiatStatus(
timestamp : java.sql.Timestamp,
Expand All @@ -22,51 +23,66 @@ private case class FiatOutStatus(status: String, transferId: Long)
private case class RegisterFiatOutAddressResponse(status : String)


class FiatChannel(context : Context) {
class FiatChannel(context : Context) extends AbstractUserChannel(context) {

def assignInAddress()(callback : Either[Error, FiatAddress] => Unit ) {
def assignInAddress() = {
val p = promise[FiatAddress]

val postData = "currency=krw"

HTTPActor.dispatcher ! PostUserResource(context, "user/fiats/address/assign", postData ) { jsonResponse =>
val response = Json.deserialize[AssignFiatInAddressResponse](jsonResponse)
val result = if (response.status == "success") Right( FiatAddress( response.bank, response.account, Some(response.owner) ) )
else Left( Error(response.status))
callback(result)
if (response.status == "success") p success FiatAddress( response.bank, response.account, Some(response.owner) )
else p failure new APIException(response.status)
}

p.future
}

def registerOutAddress(address : FiatAddress)(callback : Either[Error, FiatAddress] => Unit ) {
def registerOutAddress(address : FiatAddress) = {
val p = promise[FiatAddress]

val postData = s"currency=krw&bank=${address.bank}&account=${address.account}"

HTTPActor.dispatcher ! PostUserResource(context, "user/fiats/address/register", postData ) { jsonResponse =>
val response = Json.deserialize[RegisterFiatOutAddressResponse](jsonResponse)
val result = if (response.status == "success") Right( FiatAddress( address.bank, address.account, None ) )
else Left( Error(response.status))
callback(result)
if (response.status == "success") p success FiatAddress( address.bank, address.account, None )
else p failure new APIException(response.status)
}

p.future
}

def requestFiatOut(amount : Amount)(callback : Either[Error, FiatOutRequest] => Unit ) {
def requestFiatOut(amount : Amount) = {
val p = promise[FiatOutRequest]

val postData = s"currency=${amount.currency}&amount=${amount.value}"

HTTPActor.dispatcher ! PostUserResource(context, "user/fiats/out", postData ) { jsonResponse =>
val response = Json.deserialize[FiatOutStatus](jsonResponse)
val result = if (response.status == "success") Right( FiatOutRequest( amount.currency, response.transferId ) ) else Left( Error(response.status))
callback(result)
if (response.status == "success") p success FiatOutRequest( amount.currency, response.transferId )
else p failure new APIException(response.status)
}

p.future
}
def queryFiatOut(request : Option[FiatOutRequest] = None )(callback : Either[Error, Seq[FiatStatus]] => Unit ) {

def queryFiatOut(request : Option[FiatOutRequest] = None ) = {
val params = "currency=krw" + ( if (request == None) "" else s"&id=${request.get.id}" )
HTTPActor.dispatcher ! GetUserResource(context, s"user/fiats/status?$params" ) { jsonResponse =>
val response = Json.deserialize[Seq[FiatStatus]](jsonResponse)
callback( Right( response ) )
}
getUserFuture[Seq[FiatStatus]](s"user/fiats/status?$params")
}
def cancelFiatOut(request : FiatOutRequest)(callback : Option[Error] => Unit ) {

def cancelFiatOut(request : FiatOutRequest) = {
val p = promise[FiatOutRequest]

val postData = s"currency=${request.currency}&id=${request.id}"

HTTPActor.dispatcher ! PostUserResource(context, "user/fiats/out/cancel", postData ) { jsonResponse =>
val response = Json.deserialize[FiatOutStatus](jsonResponse)
val result = if (response.status == "success") None else Some( Error(response.status))
callback(result)
if (response.status == "success") p success request
else p failure new APIException(response.status)
}

p.future
}
}
33 changes: 9 additions & 24 deletions subprojs/tradeapi/app/api/MarketChannel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,29 +37,14 @@ case class Transaction (

case class TransactionId(id: Long)

class MarketChannel {
def ticker()( callback : Either[Error, Ticker] => Unit ) {
HTTPActor.dispatcher ! GetPublicResource("ticker" ) { jsonResponse =>
val ticker = Json.deserialize[Ticker](jsonResponse)
callback(Right(ticker))
}
}
def fullTicker()( callback : Either[Error, FullTicker] => Unit ) {
HTTPActor.dispatcher ! GetPublicResource("ticker/detailed" ) { jsonResponse =>
val fullTicker = Json.deserialize[FullTicker](jsonResponse)
callback(Right(fullTicker))
}
}
def orderbook()( callback : Either[Error, OrderBook] => Unit ) {
HTTPActor.dispatcher ! GetPublicResource("orderbook?group=true" ) { jsonResponse =>
val orderbook = Json.deserialize[OrderBook](jsonResponse)
callback(Right(orderbook))
}
}
def transactions(sinceTransactionId : TransactionId)(callback : Either[Error, Seq[Transaction]] => Unit ) {
HTTPActor.dispatcher ! GetPublicResource(s"transactions?since=${sinceTransactionId.id}" ) { jsonResponse =>
val txs = Json.deserialize[Seq[Transaction]](jsonResponse)
callback(Right(txs))
}
class MarketChannel extends AbstractChannel {
def ticker() = getPublicFuture[Ticker]("ticker")

def fullTicker() = getPublicFuture[FullTicker]("ticker/detailed")

def orderbook() = getPublicFuture[OrderBook]("orderbook?group=true")

def transactions(sinceTransactionId : TransactionId) = {
getPublicFuture[Seq[Transaction]](s"transactions?since=${sinceTransactionId.id}")
}
}
Loading

0 comments on commit 99effea

Please sign in to comment.