Sonntag, 6. Dezember 2015

Akka.NET Adventskalender – Tür 6

Arten von Nachrichten

Glückwunsch! Du hast bis zum Ende unseres ersten (leider recht theoretischen) Sprints geschafft. Nächste Woche wird es deutlich praktischer zugehen – versprochen! Aber heute werden wir noch ein paar Muster rund um Nachrichten besprechen.

Bislang haben wir hauptsächlich String-Objekte an unsere Aktoren gesandt. Es mag Situationen geben, in denen das vollkommen ausreicht, aber die Versuchung ist sicher groß, Objekte als Nachrichten zu versenden. Doch – wie sollen wir solche Objekte benennen? Vaughn Vernon nennt in seinem Buch "Reactive Messaging Patterns with the Actor Model" drei verschiedene Muster, die auch erheblichen Einfluss auf die Benennung der Klassen haben. Ich werde nachfolgend englische Klassen-Namen verwenden, wenn eure Ubiquitäre Sprache (Ubiquitous language) deutsch ist, sollten selbstverständlich deutsche Namen hier Verwendung finden.

Kommandos (Command)

Wenn Du vor hast, einem Aktor ein Kommando zu erteilen, dann ist ein imperativ die grammatikalische Form, die Du in zwischenmenschlicher Kommunikation verwenden würdest. Das würde ich durch ein Verb in Gegenwart gefolgt von einem Substantiv ausdrücken. Im englischen würden dadurch Namen entstehen wie "PrintLine", "SendMail", "Speak" oder "SplitWhatever".

Das führt dann zu sehr ausdrucksstarken Code Zeilen wie

printer.Tell(new PrintLine( ... ));
notifier.Tell(new SendMail( ... ));
speaker.Tell(new Speak( ... ));
splitter.Tell(new SplitWhatever( ... ));

Die Intention hinter solchen Zeilen sind ohne Zweifel verständlich.

Ereignisse (Event)

Wenn ein Aktor seinen internen Zustand ändert und diese Tatsache anderen mitteilen möchte, dann informiert er über etwas das soeben passierte und nicht mehr zu ändern ist. Es fand ja bereits statt. Insofern ist ein Verb in der ersten Vergangenheit mit einem vorangestellten Substantiv eine gute Wahl. Auch hier wird jeder verstehen, was eben los war. Das führt dann zu Klassen-Namen wie "LinePrinted", "MailSent", "StatusUpdated" or "Spoke".

Auch hier sind die nachfolgenden Code-Zeilen für jeden nachvollziehbar.

someone.Tell(new StatusUpdated( ... ));
someone.Tell(new MailSent( ... ));

Dokumente (Document)

Das dritte Muster für die Namensgebung, die Vaughn Vernon nennt, sind Dokument Nachrichten. Sie bestehen lediglich aus einem Substantiv und deuten an, was vom Inhalt der Nachricht zu erwarten ist. In aller Regel sind solche Nachrichten die Antwort auf eine Anfrage bei einem Aktor.

Um die Probe auf's Exempel zu machen: sind die nachfolgenden Zeilen verständlich?

Receive<GetStatus>(_ => Sender.Tell(new Status( ... )));
Receive<Read>(_ => Sender.Tell(new SensorValue( ... )));

Wie organisiert man Nachrichten?

Gute Frage. Vermutlich wird jeder hier eigene Vorlieben haben. Im Akka.NET Universum wird man vorwiegend diese Vorgehen finden:
  • Nachrichten-Klassen sind verschachtelte Klassen, die innerhalb der Aktoren definiert werden, die diese Nachrichten senden oder empfangen. Wenn Du das machst, wirst Du sehr lesbare Klassen-Namen erhalten, denn die Namen bestehen aus dem Klassen-Namen des Aktors und durch einen Punkt getrennt, der Nachrichten-Klasse. Solche Nachrichten wird man jedoch kaum bei anderen Aktoren verwenden wollen und die Aktor-Klassen werden an Länge und damit Unübersichtlichkeit zunehmen.
  • Wie bisher auch: Jede Klasse kommt in eine Datei und wenn es zu viele werden packst Du die Nachrichten Klassen in einen eigenen Ordner, den Du wahlweise als namespace nutzt.
  • Eine Misch-Form aus beiden. Es liegt bei Dir.

In unserem heutigen Beispiel werden wir die erste Methode verwenden also Nachrichten innerhalb unseres Aktors definieren.

Unser erster Aktor soll zwei Typen von Nachrichten beantworten können: Die Anfrage der aktuellen Zeit und die Bitte, auf eine Zahl 4 zu addieren. Die Antwort wird dem Absender der Anfrage übergeben.

Erzeuge eine Konsolen Anwendung mit einem Actor System wie bisher auch und erstelle diesen Aktor:

using System;
using Akka.Actor;

namespace RequestReply
{
    public class Replyer : ReceiveActor
    {
        #region command messages
        public class ReadTime {}

        public class Add4
        {
            public int Number { get; private set; }

            public Add4 (int number)
            {
                Number = number;
            }
        }
        #endregion

        public Replyer()
        {
            Receive<ReadTime>(_ =>
                Sender.Tell(String.Format("{0:HH:mm:ss}", DateTime.Now))
            );

            Receive<Add4>(add4 =>
                Sender.Tell(add4.Number + 4)
            );
        }
    }
}

Dank der eingebetteten Klassen sind die Receive<>() Aufrufe direkt verständlich und einfach. Auf der Sende-Seite hingegen müssen längere Namen getippt werden (x.Tell(new Replyer.ReadTime())). Aber verständlich ist der Code allemal. Unsere Main Methode des Konsolen-Programms könnte dann so aussehen:

public static void Main(string[] args)
{
    var system = ActorSystem.Create("Reply");
    
    var replyer = system.ActorOf(Props.Create());
    
    // Ask() will reply with a task
    Task time = replyer.Ask(new Replyer.ReadTime());
    time.Wait();
    
    Console.WriteLine("Time is: {0}", time.Result);
    
    Task number = replyer.Ask(new Replyer.Add4(17));
    number.Wait();
    
    Console.WriteLine("Number is: {0}", number.Result);
    
    Console.WriteLine("Press [enter] to continue");
    Console.ReadLine();
    
    system.Shutdown();
    system.AwaitTermination();
}

Moment mal. Bisher war immer die Rede davon, einem Actor mittels Tell() eine Nachricht zu übermitteln, nun verwenden wir plötzlich Ask(). Und noch dazu verwenden wir ominöse Wait() Aufrufe, dabei hieß es doch, dass wir bei Akka.NET niemals mit Tasks arbeiten dürfen. Stimmt dann, wenn es um das Innenleben von Aktoren geht. Da solltest Du auf die Benutzung von Tasks auf jeden Fall verzichten. Auch die Benutzung von Ask() innerhalb Aktoren spricht für schlechtes Design und sollte eher vermieden werden. Das Motto lautet "Tell() – don't Ask()".
Aber immer dann, wenn von Akka-fremdem Code (wie unsere Kommandozeilen-Applikation, aber auch eine Web API wäre ein Beispiel) Aufrufe von Akka.NET Aktoren getätigt werden sollen, die eine Antwort geben. In der Akka.NET Dokumentation zu diesem Thema ist beim Rückgabewert von einer "Future" die Rede, das passendste .NET Mittel dafür ist ein Task. Solltest Du zufällig mit async Methoden arbeiten, wirst Du diese Entscheidung der Akka.NET Entwickler lieben!

So, die Grundlagen hättest Du erfolgreich überstanden. Leider sind wir an vielen Stellen nicht zu tief eingestiegen, ich hoffe dennoch, dass ich einen angenehmen Überblick über die grundlegendsten Dinge rund um Akka.NET vermitteln konnte. Morgen starten wir mit einem neuen Sprint, in dem wir uns mit einzelnen Aktoren und deren Zusammenspiel befassen werden. Ich hoffe, Du bist wieder dabei.

Keine Kommentare:

Kommentar veröffentlichen