Suche
Enterprise Tales

(Funktionales) Skalieren mittels Messaging

Lars Röwekamp und Matthias Weßendorf

Zur Kommunikation innerhalb von verteilten Systemen gibt es eine Reihe von Möglichkeiten. Neben schwergewichtigen Web-Service-Ansätzen (SOAP) kann der geneigte Entwickler beispielsweise auch auf leichtgewichtigere Varianten wie Remote Procedure Calls (RPC) oder HTTP REST zurückgreifen. Eine weitere, häufig unterschätzte Alternative stellt das Messaging dar.

Asynchrones Arbeiten: Einfache Integration neuer Anforderungen

Bei der Verwendung von Web Services oder RPCs erfolgt der Aufruf eines entfernten „Dienstes“ in der Regel synchron. Das bedeutet, dass ein aufrufendes Programm erst dann fortgesetzt wird, wenn der aufgerufene Dienst seine Arbeit erledigt und das Ergebnis zurückgeliefert hat. Das kann für den aufrufenden Client, insbesondere bei lang laufenden Methoden, ein Problem darstellen. Messaging dagegen basiert auf einem asynchronen Ansatz: Ein Client schickt eine Nachricht an einen Server, der auf die eintreffenden Nachrichten in einer beliebigen Art und Weise reagiert. Das Clientprogramm kann direkt nach dem Absenden der Nachricht weiterarbeiten. Aber nicht nur die eben beschriebene Reduktion von Latenzzeiten ist ein valider Grund für den Einsatz von Messaging-Lösungen, wie beispielsweise JMS [1] oder AMQP [2]. Durch die Entkopplung der Systeme lassen sich auch neue fachliche Anforderungen deutlich einfacher umsetzten.

Ein traditionelles Beispiel

Als Beispiel soll ein JSF-basiertes Onlineredaktionssystem dienen, das beim Veröffentlichen eines (Online-)Artikels verschiedene Aktionen vornimmt. Die Action-Methode eines auf CDI aufbauenden JSF Controllers könnte wie in Listing 1 aussehen. Das verdeutlicht mehrere Probleme: Die JSF-Controller-Methode publishArticle()ruft der Reihe nach drei verschiedene Systeme auf, die alle unterschiedliche Antwortzeiten haben (können). Dabei erfolgt der Aufruf des jeweils nächsten Systems immer erst nach der vollständigen Abarbeitung seines Vorgängers. Besonders beim „posten“ auf externe Webressourcen, wie im Fall von Twitter, können jede Menge unterschiedlicher Probleme und Latenzen auftreten, die hier im Detail nicht behandelt wurden. Erst nach der Abarbeitung aller aufgerufenen Systeme kann die JSF-Applikation mit ihrer Ausführung fortfahren. Sollen später übrigens noch weitere Systeme mithilfe des JSF Controllers über den neuen Artikel informiert werden, so muss der Controller-Code allerdings erneut angepasst werden.

Listing 1
@Named
@RequestScoped
public class PublishArticle{  @Inject PublisherService publisherService;
  @Inject AuthorService authorService;
  @Inject TwitterService twitterService;

  @Inject Article article;
  ...
  public String publishArticle()  {
    URL articleUrl = publisherService.publishArticle(article);
    authorService.notifyAboutPublishedArticle(article.getAuthor(), articleURL);
    twitterService.post(article.getTitle(), url);
    return "publishe";  }
}
Beispiel mit Messaging

Werfen wir noch einmal einen Blick auf das Redaktionssystem, diesmal nehmen wir ein Messaging-Systems hinzu (Listing 2). Statt dass der JSF Controller eigenhändig zahlreiche Systeme aufruft, versendet er nun lediglich eine (JMS) Nachricht. Listener, die an dem Veröffentlichen eines (Online-)Artikels interessiert sind, um beispielsweise den Link auf Twitter zu posten, können so autonom von dem JSF Controller entwickelt und gewartet werden (Listing 3). Der Code für das Gesamtsystem, z. B. der des JSF Controllers oder der eines (JMS) MessageListeners, wird durch die Trennung deutlich übersichtlicher. Zusätzlich wird die Fachlichkeit, wie die Benachrichtigung eines Autors, einer Interessentengruppe etc., sauber voneinander getrennt (Separation of Concerns). Diese Tatsache führt zu Code, der wesentlich wartbarer ist.

Listing 2
@Named
@RequestScoped
public class publishArticle{  @Inject Connection connection;
  @Inject MessageProducer producer;
  ...

  @Inject Article article;
  ...
  public String publishArticle()  {
    ...
    Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
    ...
    ObjectMessage message =  session.createObjectMessage();
    message.setObject(article);
    
    // send the article message to various (sub) systems
    producer.send(message);
    connection.close();
    return "published";  }
}
Listing 3
public class TwitterPoster implements MessageListener {
    @Inject TwitterService twitterService;
    ...
    @Override
    public void onMessage(Message message) {
        Article article = (Article) ((ObjectMessage) message).getObject();
        // post information about this article...
        twitterService.post(article.getTitle(), article.getOnlineUrl());
    }
}
Geschrieben von
Lars Röwekamp und Matthias Weßendorf
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.