15. Model Microservice in Akka HTTP
case class Prediction(id: Long, timestamp: Long, value: Double)
trait Protocols extends DefaultJsonProtocol {
implicit val predictionFormat = jsonFormat3(Prediction)
}
16. Model Microservice in Akka HTTP
def model(features: Map[Char, Double]) = {
val coefficients = ('a' to 'z').zip(1 to 26).toMap
val predictionValue = features.map {
case (identifier, value) =>
coefficients.getOrElse(identifier, 0) * value
}.sum / features.size
Prediction(Random.nextLong(), System.currentTimeMillis(), predictionValue)
}
def parseFeatures(features: String): Map[String, Double] = {
features.parseJson.convertTo[Map[Char, Double]]
}
def predict(features: String): Prediction = {
model(parseFeatures(features))
}
17. Model Microservice in Akka HTTP
trait Service extends Protocols {
implicit val system: ActorSystem
implicit def executor: ExecutionContextExecutor
implicit val materializer: Materializer
val logger: LoggingAdapter
val routes = {
logRequestResult("model-service") {
pathPrefix("predict") {
(get & path(Segment)) {
features: String =>
complete {
ToResponseMarshallable(predict(features))}}}}}}
28. Model Server in http4s
object Models {
val modelA = HttpService {
case GET -> Root / "a" / inputData =>
val response = true
Ok(s"Model A predicted $response.")
}
val modelB = HttpService {
case GET -> Root / "b" / inputData =>
val response = false
Ok(s"Model B predicted $response.")
}
}
29. Model Server in http4s
object Client {
val client = PooledHttp1Client()
private def call(model: String, input: String) = {
val target = Uri.fromString(s"http://localhost:8080/models/$model/$input").toOption.get
client.expect[String](target)
}
def callA(input: String) = call("a", input)
def callB(input: String) = call("b", input)
}
30. Model Server in http4s
def splitTraffic(data: String) = {
data.hashCode % 10 match {
case x if x < 4 => Client.callA(data)
case _ => Client.callB(data)
}
}
31. Model Server in http4s
object ModelServer extends ServerApp {
val apiService = HttpService {
case GET -> Root / "predict" / inputData =>
val response = splitTraffic(inputData).run
Ok(response)
}
override def server(args: List[String]): Task[Server] = {
BlazeBuilder
.bindLocal(8080)
.mountService(apiService, "/api")
.mountService(Models.modelA, "/models")
.mountService(Models.modelB, "/models")
.start
}
}
32. Model Server in http4s
import scala.util.Random
val modelC = HttpService {
case GET -> Root / "c" / inputData => {
val workingOk = Random.nextBoolean()
val response = true
if (workingOk) {
Ok(s"Model C predicted $response.")
} else {
BadRequest("Model C failed to predict.")
}
}
}
33. Model Server in http4s
private def call(model: String, input: String): Task[Response] = {
val target = Uri.fromString(
s"http://localhost:8080/models/$model/$input"
).toOption.get
client(target)
}
def callC(input: String) = call("c", input)
34. Model Server in http4s
def splitTraffic(data: String) = {
data.hashCode % 10 match {
case x if x < 4 => Client.callA(data)
case x if x < 6 => Client.callB(data)
case _ => Client.callC(data)
}
}
val apiService = HttpService {
case GET -> Root / "predict" / inputData =>
val response = splitTraffic(inputData).run
response match {
case r: Response if r.status == Ok => Response(Ok).withBody(r.bodyAsText)
case r => Response(BadRequest).withBody(r.bodyAsText)
}
}
35. Model Server in http4s
def splitTraffic(data: String) = {
data.hashCode % 10 match {
case x if x < 4 => Client.callA(data)
case x if x < 6 => Client.callB(data)
case _ => Client.callC(data)
}
}
val apiService = HttpService {
case GET -> Root / "predict" / inputData =>
val response = splitTraffic(inputData).run
response match {
case r: Response if r.status == Ok => Response(Ok).withBody(r.bodyAsText)
case r => Response(BadRequest).withBody(r.bodyAsText)
}
}
36. Model Server in http4s
private def call(model: String, input: String) = {
val target = Uri.fromString(s"http://localhost:8080/models/$model/$input").toOption.get
client(target).retry(Seq(1 second), {_ => true})
}
41. Circuit Breakers in Erlang/Elixir
def update(user_data) do
{user_id, _} = user_data
case verify_fuse(user_id) do
:ok ->
write_to_db(user_data)
|> parse_db_response(user_id)
:sorry ->
IO.puts "This user can't be updated now. ¯_(ツ)_/¯"
:sorry
end
end
42. Circuit Breakers in Erlang/Elixir
defp verify_fuse(user_id) do
case :fuse.ask(user_id, :sync) do
{:error, :not_found} ->
IO.puts "Installing"
install_fuse(user_id)
:ok
:blown ->
:sorry
_ -> :ok
end
end
43. Circuit Breakers in Erlang/Elixir
defp install_fuse(user_id) do
:fuse.install(user_id, {{:standard, 3, 60000}, {:reset, 900000}})
end
44. Circuit Breakers in Erlang/Elixir
defp write_to_db({_user_id, _data}) do
Enum.random([{:ok, ""}, {:error, "The DB dropped the ball. ಠ_ಠ"}])
end
45. Circuit Breakers in Erlang/Elixir
defp parse_db_response({:ok, _}, _user_id) do
:ok
end
defp parse_db_response({:error, message}, user_id) do
:fuse.melt(user_id)
IO.puts "Error encountered: #{message}.nMelting the fuse!"
:sorry
end
46. Circuit Breakers in Erlang/Elixir
def update(user_data) do
{user_id, _} = user_data
case verify_fuse(user_id) do
:ok ->
write_to_db(user_data)
|> parse_db_response(user_id)
:sorry ->
IO.puts "This user can't be updated now. ¯_(ツ)_/¯"
:sorry
end
end
47. Circuit Breakers in Akka
class DangerousActor extends Actor with ActorLogging {
import context.dispatcher
val breaker =
new CircuitBreaker(
context.system.scheduler,
maxFailures = 5,
callTimeout = 10.seconds,
resetTimeout = 1.minute).onOpen(notifyMeOnOpen())
def notifyMeOnOpen(): Unit =
log.warning("My CircuitBreaker is now open, and will not close for one minute")
}