Akka podstawy część 3 – Akka Java 8
Czy możemy używając aktorów pobawić się elementami funkcyjnymi z Java 8?
Jasne.
Akka ma specjalne interfejsy do obsługi Java 8. Są one na razie oznaczone jako experimental, ale sprawdzić je w działaniu warto !
Używać będę najnowszej aktualnie Akki w wersji 2.3.9
Tym razem nie użyję activatora, tylko zrobię projekt ręcznie za pomocą mvn. Cały projekt na github – https://github.com/najavie/akka-mvn
Cała instalacja za pomocą mvn sprowadza się do dodania dependency do pom.xml:
1 2 3 4 5 |
<dependency> <groupId>com.typesafe.akka</groupId> <artifactId>akka-actor_2.10</artifactId> <version>2.3.9</version> </dependency> |
Czas napisać jakiegoś aktora…
Mój aktor będzie robił prostą rzecz – wypisywał na ekran to co mu prześlę w formie stringa.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package pl.najavie.actors; import akka.actor.AbstractActor; import akka.actor.Props; import akka.japi.pf.ReceiveBuilder; /** * Created by krzysztof on 1/21/15. */ public class RepeaterSimple extends AbstractActor { RepeaterSimple() { receive(ReceiveBuilder.match(String.class, message -> System.out.println(message)).build()); } public static Props props() { return Props.create(RepeaterSimple.class, RepeaterSimple::new); } } |
W porównaniu do „zwykłego” aktora używającego UntypedActor, kod nie wygląda na prosty… ale popatrzmy na różnice…
Pierwsza jaka się rzuca w oczy – AbstractActor zamiast UntypedActor. Spoko.
Następnie mamy konstruktor, który ma uruchomianą metodę receive… to praktycznie to samo co metoda onReceive() z implementacji UntypedActor, natomiast z kilkoma różnicami – jako parametr przyjmuje ona klasy typu PartialFunction (o nich za chwilkę). I mamy helpera ReceiveBuildera – jest to UWAGA – trait scalowy! Tak, zaczynamy powoli wchodzić w zabawę ze scalą, ale to Java, więc nie będziemy nic w niej pisać, jedynie użyjemy jakiegoś jej elementu 🙂 Trait to taki bardziej rozbudowany interfejs, nie ma konieczności, aby się na tym w tej chwili znać.
Ważne jest to, że metoda receive() jako parametr przyjmuje PartialFunction, a ReceiveBuilder nam to udostępnia…
W metodzie match mamy podobnie jak w przypadku onReceive zwykłego aktora – porównanie do Obiektu (w moim przypadku do Stringa, i następnie obsługę wiadomości, jako funkcji.. I na samym końcu mamy metodę build, która mi z tego zbuduje PartialFunction.
Wydaje się proste? Bo takie jest !
Jeszcze na koniec statyczna metoda Props, która nam stworzy aktora. Props to „konstruktor” aktora. Nie jest dobrą i polecaną praktyką tworzenie aktorów poza systemem aktorów, należy to robić poprzez jakiś taki factory method z pomocą klasy Props. Dzięki niej możemy też przekazywać parametry dla konstruktora aktora, ale to pokażę pod koniec wpisu.
Ok… to teraz odpalmy tego aktora i niech coś powie!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package pl.najavie; import akka.actor.ActorRef; import akka.actor.ActorSystem; import akka.actor.Props; import pl.najavie.actors.RepeaterSimple; /** * Hello world! * */ public class App { public static void main( String[] args ) { ActorSystem system = ActorSystem.create("system"); ActorRef repeater = system.actorOf(Props.create(RepeaterSimple.class),"repeaterSimple"); repeater.tell("HELLO WORLD!",ActorRef.noSender()); system.shutdown(); } } |
Tutaj bez niespodzianek, właściwie ta sama składnia. Jak uruchomimy przykład to bez niespodzianek – hello world! wypowiedziany przez aktora…
Dobra, a co jeśli bym chciał obsłużyć więcej niż jeden typ wiadomości? Różne obiekty? Da się to zrobić bardzo prosto!
Oto nieco przerobiony aktor 🙂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
package pl.najavie.actors; import akka.actor.AbstractActor; import akka.actor.Props; import akka.japi.pf.ReceiveBuilder; import scala.PartialFunction; /** * Created by krzysztof on 1/21/15. */ public class Repeater extends AbstractActor { public Repeater() { receive(repeat().orElse(notFound())); } PartialFunction repeat() { return ReceiveBuilder.match( String.class, message -> System.out.println(message) ).build(); } PartialFunction notFound() { return ReceiveBuilder.match( Object.class, message -> System.out.println("NO METHOD FOR OBJECT: " + message.getClass()) ).build(); } public static Props props() { return Props.create(Repeater.class, Repeater::new); } } |
I tutaj zaczyna się dopiero fajna zabawa!
Kod zaczyna być bardziej przejrzysty, wydzielamy metody…
Po kolei…
stworzyłem dwie metody –
repeat() oraz notFound()
Obie metody zwracają PartialFunction
PartialFunction w swoim traicie ma fajne funkcjonalności… tworzenia funkcji z wielu funkcji… i do tego są właśnie PartialFunctions. Tworzymy częściową implementację w jednym miejscu, w innym kolejną, nastepnie robimy z nich łańcuszek…
Spójrzmy na metodę notFound() – jeśli cokolwiek wejdzie co nie podpasuje pod Stringa poleci do metody notFound.
To teraz uruchomimy tego aktora…
1 2 3 |
ActorRef repeater = system.actorOf(Props.create(Repeater.class),"repeater"); repeater.tell("Hello from repeater", ActorRef.noSender()); repeater.tell(new Date(),ActorRef.noSender()); |
Raz wysyłam Stringa, raz wysyłam date… pewnie bez niespodzianek raz dostane stringa, raz komunikat o błędzi
Jeszcze jedna sprawa została. Jakbym chciał jakoś sparametryzować aktora? Jak uruchomić go z jakimiś parametrami? Jak zauważyliście aktora nie tworzymy przez proste „new JakisAktor()”. A więc jak to zrobić?
Bardzo prosto 🙂 Odpowiedzią jest klasa Props.
Utworzyłem sobie takiego prostego aktora:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package pl.najavie.actors; import akka.actor.AbstractActor; import akka.actor.Props; import akka.japi.pf.ReceiveBuilder; import java.util.stream.IntStream; /** * Created by krzysztof on 1/24/15. */ public class RepeaterWithParams extends AbstractActor { public static Props props(Integer count) { return Props.create(RepeaterWithParams.class, () -> new RepeaterWithParams(count)); } private final Integer count; public RepeaterWithParams(Integer count) { this.count = count; receive(ReceiveBuilder.match(String.class, message -> IntStream.range(0, count).forEach(element -> System.out.println(message)) ).build()); } } |
I proszę bardzo – Props może mieć metody z parametrem, i on zajmuje się prawidłowym tworzeniem aktora(też z parametrami 🙂 ). Proste i prawie tak samo łatwe jakbyśmy pracowali ze zwykłym obiektem…
Definicja takiego aktora wygląda tak:
1 2 |
ActorRef repeaterWithParams = system.actorOf(Props.create(RepeaterWithParams.class,5),"repeaterWithParams"); repeaterWithParams.tell("Hello from repeaterWithParams", ActorRef.noSender()); |
Proste? Tworzenie za pomocą Props, podanie klasy aktora, i w parametrze 5tka 🙂
Po odpaleniu brak niespodzianek.
Jak widać, tworzenie u używanie aktorów razem z Java 8 nie jest trudne, dodaje kilka ciekawych funkcjonalności poprzez dodanie PartialFunction, i jeśli to fajnie ułożymy, możemy pisać aktorów z nieco większą … elegancją 🙂 Pamiętajmy jednak, że są to funkcjonalności póki co oznaczone jako experimental, więc nie do końca jeszcze ukończone, nie do końca wydajne, ale warto się z nimi już zaznajomić, aby za jakiś czas zacząć pisać tylko w ten sposób 🙂