Skip to content

Instantly share code, notes, and snippets.

@angeloh
Last active August 29, 2015 14:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save angeloh/d26f1ad295feb23ac1b2 to your computer and use it in GitHub Desktop.
Save angeloh/d26f1ad295feb23ac1b2 to your computer and use it in GitHub Desktop.
Cameo Patterns
/*
https://www.chrisstucchio.com/blog/2013/actors_vs_futures.html
First, I'd argue that you've excluded Akka's most important use case: Distribution. I would say
"Futures for Concurrency, Actors for State or Distribution." Location transparency is one of
Akka's most important use cases - if you use Actors infront of services, then you can deploy
your application in many discrete micro-services with ease, only changing configuration. For this,
you use actorSelection and pass the path so you can work on the app as one big piece and then
deploy it as 7.
I think this is a decent rule of thumb - futures for concurrency, actors for state - but there
is one particular use case where I've come to believe that actors may actually be a better
fit than futures.
If collecting data from endpoints and combining it, you can use an actor to collect the
results together (See Jamie Allen, Effective Akka, Ask and Cameo Patterns). The responses
become state in this one short lived actor, instead of being more functional with monadic futures.
Each Ask creates a future and an extra temp actor in the actor system so has more overhead
than futures alone but you can eliminate all but one extra actor and all but one future
using an extra pattern.
This may be more efficient than either futures alone or ask, as you can collect the data by
using Tells with the sender specified as an extra actor whose life cycle consists only of
collecting the results for the request. You can use tell, avoiding any asks as well. Some
preliminary testing on scala shows that this may actually be more efficient than trying to
combine together results from futures, and it also gets rid of all of the nasty timeout
stack traces - if you don't get the data from an endpoint in time, schedule a message to
send to the actor and then have it log the exact details of the missing data/failure.
It's a bit wordier than just using futures though so that's a downside. But it can be a
bit more explicit about what can go wrong and might allow you more flexibility in handling
those scenarios where you get no result back from a service or two vs composing futures.
Here is an example of one result type so might not be the best example - this approach
is much better suited if you're trying to receive multiple types. Eg from 3
different services, if you expect 3 different result types, then you can look
at extra and cameo.
I found with Java that the performance is apx equivalent between using futures
and actors - with Scala there is more disparity in the performance (likely tuning
the threadpool would fix this). More-so the non-functionals like logging are a
big reason to consider using actors for retrieving data from multiple services.
*/
class ArticleParseWithFuturesSpec extends FlatSpec with Matchers {
import scala.concurrent.ExecutionCont...
"ArticleParser" should "do work concurrently with futures" in {
val futures = (1 to 2000).map(x => {
Future(ArticleParser.apply(TestHelper.file))
})
TestHelper.profile(() => Await.ready(Future.sequence(futures), 20 seconds), "Futures")
}
}
class ArticleParseWithActorsSpec extends FlatSpec with Matchers {
val system = ActorSystem()
val workerRouter: ActorRef =
system.actorOf(
Props.create(classOf[ArticleParseActor]).
withDispatcher("my-dispatcher").
withRouter(new RoundRobinPool(8)), "workerRouter")
"ArticleParseActor" should "do work concurrently" in {
val p = Promise[String]()
val cameoActor: ActorRef =
system.actorOf(Props(new CameoActor(p)))
(0 to 2000).foreach(x => {
workerRouter.tell(
new ParseArticle(TestHelper.file)
, cameoActor);
})
TestHelper.profile(() => Await.ready(p.future, 20 seconds), "Actors")
}
}
class CameoActor(p: Promise[String]) extends Actor {
var count = 0
override def receive: Receive ={
case _ => count = count + 1
if(count == 2000) p.complete(Try("ok"))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment