In this talk, we will review an experience of rearchitecting and migrating a system that appeared reactive and microservice-based, but was in fact a monolith with RPC calls to a truly reactive architecture.
The migration work had to be done without causing disruption to the current system, and without taking time to rewrite the system. The result is a biometric computer vision system with a distributed domain in Akka / Scala with storage in Apache Cassandra, with the computer vision components in OpenCV in C++, connected with RabbitMQ and with batch analytics code in Apache Spark.
This talk will show the architectural and code smells that were the result of half-harted reactive implementation and the way to address them, but also the impact of the changes on privacy and security of the stored biometric information.
21. CC BY-NC 3.0
At least once delivery I
class SceneClassifierActor(…) extends Actor {
private[this] val kafkaConsumerActor = context.actorOf(…)
private[this] val producer = KafkaProducer(…)
override def receive: Receive = {
case extractor(consumerRecords) =>
val futures = consumerRecords.pairs.flatMap {
case (Some(handle), envelope) =>
val outEnvelope = …
Some(producer.send(KafkaProducerRecord("scene", handle, outEnvelope)))
}
import context.dispatcher
Future.sequence(futures).onSuccess {
case _ => kafkaConsumerActor ! Confirm(consumerRecords.offsets, commit = true)
}
}
}
22. CC BY-NC 3.0
At least once delivery II
class IdentityMatcherActor(...) extends PersistentActor with AtLeastOnceDelivery {
override val persistenceId: String = "identity-matcher-actor"
def identifyFacesAndSend(identifyFaces: Seq[IdentifyFace])(implicit executor: ExecutionContext): Future[Unit] = {
// Future.sequence(producer.send(...))
}
def handleIdentifyFace: Receive = {
case (deliveryId: Long, identifyFaces: IdentifyFaces) =>
import context.dispatcher
identifyFacesAndSend(identifyFaces.identifyFaces).onSuccess { case _ => confirmDelivery(deliveryId) }
case IdentifyFaces(faces) =>
import context.dispatcher
identifyFacesAndSend(faces).onFailure { case _ => self ! Kill }
}
override def receiveRecover: Receive = handleIdentifyFace
override def receiveCommand: Receive = handleIdentifyFace orElse {
case extractor(consumerRecords) =>
val identifyFaces = consumerRecords.pairs.flatMap {
case (Some(handle), envelope) =>
Some(IdentifyFace(envelope.ingestionTimestamp, envelope.correlationId, handle, envelope.payload))
}
persist(IdentifyFaces(identifyFaces = identifyFaces)) { result =>
deliver(self.path)(deliveryId => (deliveryId, result))
sender() ! Confirm(consumerRecords.offsets, commit = true)
}
}
}
23. CC BY-NC 3.0
At most once delivery
class DashboardSinkActor(...) extends Actor {
private[this] val kafkaConsumerActor = context.actorOf(...)
override def receive: Receive = {
case extractor(consumerRecords) =>
consumerRecords.pairs.foreach {
case (None, _) =>
case (Some(handle), envelope) =>
context.system.eventStream.publish(
TweetEnvelope(version = 100,
handle = handle,
ingestionTimestamp = envelope.ingestionTimestamp,
messageId = envelope.messageId,
messageType = envelope.messageType,
payload = envelope.payload))
}
kafkaConsumerActor ! Confirm(consumerRecords.offsets, commit = true)
}
}
26. MANCHESTER LONDON NEW YORK
CC BY-NC 3.0
github.com/eigengo/reactive-summit-2016
rsa16-models.s3-website-eu-west-1.amazonaws.com/{identity,scene}/{config,labels,params}
@honzam399
janm@cakesolutions.net