I recently was on a seminar about Akka with @DanielSawano and @DanielDeogun. They told us about how it works and there lessons learned when using it in a real business case (you can hear about it at Jfocus 3-5 February 2014 in Stockholm).
So I thought let’s test it with a simple kata. So I choose the FizzBuzz kata. It’s a simple an trivial parallel problem that seems perfect to try Akka with. I’m going to do the kata in java even thou Akka is written in Scala.
The FizzBuzz kata is as simple as printing out Fizz on numbers divided with 3 an Buzz on numbers divided by 5. If a number can be divided with both 3 and 5 the it shall print FizzBuzz.
So first let’s start with the mandatory test.
public class FizzBuzzActorTest { static ActorSystem system; @Before public void createSystem() { system = ActorSystem.create(); } @Test public void shouldCreateInitialActor() throws Exception { final Props props = Props.create(FizzBuzzActor.class); final TestActorRef<FizzBuzzActor> ref = TestActorRef.create(system, props, "testB"); final Future<Object> future = akka.pattern.Patterns.ask(ref, "Test", 3000); assertTrue(future.isCompleted()); assertEquals("Hello Test", Await.result(future, Duration.Zero())); } }
To make it pass we need to implement the FizzBuzzActor as following
public class FizzBuzzActor extends UntypedActor { @Override public void onReceive(Object message) throws Exception { if (message instanceof String) { getSender().tell("Hello Test", getSelf()); } else { unhandled(message); } } }
All green, this will ensure that Akka will work and that we have our test environment configured.
Then let’s implement the FizzBuzz algorithm. I will do that in a pojo first to test the correctness of algorithm. And then let the worker actors us it for the calculation.
public class FizzBuzzUtilTest { private final FizzBuzzUtil util = new FizzBuzzUtil(); @Test public void shouldReturnANumber() { assertEquals("1", util.fizzBuzzOrNumber(1)); } @Test public void shuldReturnFizz() { assertEquals("Fizz", util.fizzBuzzOrNumber(3)); } @Test public void shuldReturnBuzz() { assertEquals("Buzz", util.fizzBuzzOrNumber(5)); } @Test public void shuldReturnFizzBuzz() { assertEquals("FizzBuzz", util.fizzBuzzOrNumber(15)); } }
public class FizzBuzzUtil { public String fizzBuzzOrNumber(int number) { if (number % 3 == 0 && numrber % 5 == 0) return "FizzBuzz"; else if (number % 3 == 0) return "Fizz"; else if (number % 5 == 0) return "Buzz"; return String.valueOf(number); } }
The worker actors will use the FizzBuzzUtil to do there calculation when they receive an integer as a message. So we need some way to create the workers so lets create a Boss actor that controls the calculation, also this one is tested with unit-tests (not shown here).
public class FizzBuzzCalculatorActor extends UntypedActor { private CalculationPackage calculationPackage; private String[] resultArray; private int recivedResults; private long startTime; @Override public void onReceive(Object message) throws Exception { if (message instanceof CalculationResult) { onCalculationResult((CalculationResult) message); } else if (message instanceof CalculationPackage) { onCalculationPackage((CalculationPackage) message); } else { unhandled(message); } } private void onCalculationPackage(CalculationPackage message) { initiateState(message); startFizzBuzzCalculation(); } private void startFizzBuzzCalculation() { ActorRef actorRef = getContext().actorOf(Props.create(FizzBuzzActor.class).withRouter(FromConfig.getInstance()), "router"); for (int i = 0; i <= calculationPackage.toNumber(); ++i) { actorRef.tell(i, getSelf()); } } private void initiateState(CalculationPackage message) { calculationPackage = message; resultArray = new String[calculationPackage.toNumber() + 1]; recivedResults = 0; startTime = System.currentTimeMillis(); } private void onCalculationResult(CalculationResult result) { resultArray[result.number()] = result.result(); if (recivedResults++ == calculationPackage.toNumber()) { System.out.println(String.format("Total calcualtion time %.03f sec", (System.currentTimeMillis() - startTime) / 1000.f)); getContext().system().shutdown(); } } }
The configuration for the router is read from the application.conf that Akka uses. This makes it really simple to change the number of instances or the routing algorithm.
Now we can give the program a fair amount of data to work with say 0 to 5 millions. Them main method is used to initialize the system and defining the amount of data to work with.
public class FizzBuzzCalculator { public static void main(String[] args) { ActorSystem system = ActorSystem.create("FizzBuzzCalulator"); ActorRef actorRef = system.actorOf(Props.create(FizzBuzzCalculatorActor.class), "FizzBuzzCalculatorActor"); actorRef.tell(new CalculationPackage(5000000), ActorRef.noSender()); } }
The time to run that was, on my computer, 12.956 sec.
The implementation is not perfect in any sense. It even violates some of the best practises that such as that actor should not have states.
So this was how I solved the FizzBuzz kata with Akka with my first try. All code is available at github.