Last active
August 29, 2015 14:21
-
-
Save angeloh/d26f1ad295feb23ac1b2 to your computer and use it in GitHub Desktop.
Cameo Patterns
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
/* | |
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