<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
In diesem Abschnitt werden einige Techniken beleuchtet, die im Datenbankprogramm Eingang finden.
Diese sind sinnvoll, um für die weiteren Programmieraufgaben eine solide Basis zu haben. Einiges mag im ersten Moment aufwändig erscheinen, bewährt sich aber mit steigender Komplexität der Web-Anwendung.
Der Code der Anwendung befindet sich hier.
Insbesondere während der Entwicklung ist es praktisch, wenn ein Programm an bestimmten Stellen Informationen ausgibt. Dies ist in Java natürlich über System.out.println()
möglich, allerdings gibt es für dieses Szenario auch eine häufig verwendete Bibliothek, die speziell für die Ausgabe von Logging-Informationen konzipiert wurde. Der Vorteil liegt darin, dass diese flexibel zu konfigurieren und einfach einzusetzen ist.
Im Folgenden wird die Bibliothek Logback vorgestellt, die in vielen Projekten Verwendung findet.
Per Maven kann die Bibliothek über
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
XML
eingebunden werden.
In jedem Programmteil, der Logging-Informationen ausgeben soll, wird eine Instanz des Loggers erstellt, z. B.
Logger LOGGER = LoggerFactory.getLogger("meinLogger");
Java
In diesem Fall heißt der Logger meinLogger
, was dazu genutzt werden kann, um über den Namen eine Konfiguration für diesen Logger vorzunehmen. Dazu gleich mehr, zunächst aber: wie wird der Logger verwendet?
LOGGER.info("Programm wurde erfolgreich gestartet");
Java
Das info()
legt fest, dass der Text nur dann ausgegeben wird, wenn das Debug-Level auch die Ausgabe von Informationen ausgibt.
Wenn ein Fehler ausgegeben werden soll, so würde man das folgendermaßen machen:
LOGGER.error("Es ist ein Fehler aufgetreten!");
Java
Neben info
und error
gibt es noch weitere Debug-Level:
TRACE < DEBUG < INFO < WARN < ERROR
Aus vertx heraus kann man in der Konfigurationsdatei logback.xml
direkt in source/main/resources
festlegen, ab welchem Debug-Level unser Logger meinLogger etwas ausgibt. So könnte dort unter anderem beispielsweise die folgende Zeile stehen:
<logger name="meinLogger" level="info"/>
XML
Das bedeutet, dass unser Logger immer dann etwas ausgibt, wenn es sich vom Level her mindestens um eine info
handelt. Ausgaben auf trace
-Level würden ignoriert werden.
vertx zeichnet sich dadurch aus, dass sehr viele Anfragen von Clients an den Server gestellt werden können, ohne dass dieser Probleme hätte, diese Anfragen zu beantworten. Dabei ist es besonders wichtig, dass die Abarbeitung einer Anfrage nicht die Anfragen anderer Clients verlangsamt.
Ein gutes Beispiel dafür ist der Zugriff auf eine Datenbank: bis eine komplexe Suchanfrage abgearbeitet wurde, können schon mal mehrere Sekunden vergehen. Wenn in dieser Zeit keine anderen Anfragen bearbeitet werden könnten, wäre das für eine Web-Seite von Nachteil.
Aus diesem Grund ist es üblich, dass beispielsweise eine Datenbankabfrage zwar aufgerufen wird, das Hauptprogramm danach aber direkt fortfährt. Sobald die Daten der Datenbank vorliegen, werden die nächsten passenden Schritte durchgeführt.
Man wartet also nicht ab, bis das Ergebnis vorliegt und blockiert dabei alles andere (das wäre synchron), sondern lässt die Datenbankabfrage parallel zum Hauptprogramm laufen und wenn die Ergebnisse vorliegen, werden diese erst dann weiterverarbeitet. Es lässt sich nicht vorhersagen, wann die Rückmeldung der Datenbank eintrifft (asynchron).
Manchmal ist es notwendig, dass man einen asynchronen Aufruf abwartet, ehe man einen weiteren asynchronen Aufruf startet. Ein Beispiel wäre, dass man eine Datenbank zunächst anlegt (asynchroner Prozess) und wenn dies geschehen ist, könnten erste Benutzer darin angelegt werden (wieder ein asynchroner Prozess).
Für solche Zwecke sind Futures
in vertx ein brauchbares Mittel:
public void start(Future<Void> startFuture) { Future<Void> datenbankFuture = erstelleDatenbank() .compose(db -> erstelleUser("user", "geheim")); datenbankFuture.setHandler(db -> { if (db.succeeded()) { LOGGER.info("Datenbank initialisiert"); startFuture.complete(); } else { LOGGER.info("Probleme beim Initialisieren der Datenbank"); startFuture.fail(db.cause()); } }); }
Java
1 | startFuture wird erstellt, um Informationen aufzunehmen, ob der gesamte Prozess funktioniert hat oder nicht |
2 | Zunächst wird die Datenbank erstellt (asynchron) und … |
3 | … mittels compose im Anschluss auch ein neuer Benutzer erzeugt (auch asynchron). |
4 | datenbankFuture enthält Informationen, ob die Operationen erfolgreich waren: |
5 | succeeded nur dann, wenn beide Aufrufe (erstelleDatenbank() und erstelleUser() ) erfolgreich waren |
6 | Man kann auch von Hand festlegen, dass eine asynchrone Operation erfolgreich war mittels complete |
7 | Im Falle eines Fehlschlags kann man mittels fail eine Fehlermeldung an die aufrufende Instanz weitergeben. Dort wird sie mittels cause ausgelesen. |
Die obige Methode kann von außen beispielsweise aufgerufen werden über:
Future<String> starteDatenbankVerticle = Future.future(); vertx.deployVerticle(new DatenbankVerticle()); (1) starteDatenbankVerticle.setHandler(db->{ (2) if (db.succeeded()) { LOGGER.info("War erfolgreich!"); else { LOGGER.info("Fehlgeschlagen: "+db.cause()); } });
Java
1 | Ruft automatisch die start -Methode im Datenbank-Verticle auf. |
2 | Dieser Abschnitt wird aufgerufen, wenn der Aufruf der Datenbank zurückkehrt. |
Unter dem Eventbus kann man sich die Kommunikationszentrale von vertx vorstellen. Darüber können unterschiedliche Programmteile von vertx miteinander kommunizieren, auch über mehrere Rechner hinweg.
Die Grundidee ist denkbar einfach: Für jedes Thema wird im einfachsten Fall eine Adresse festgelegt.
An diese Adresse kann man etwas versenden und die Gegenstelle kann bei Erhalt einer Nachricht darauf reagieren und gegebenenfalls eine Antwort zurückschicken.
Die Adresse könnte lauten:
String EB_ADRESSE = "vertxdatabase.eventbus";
Java
Hier wird die Anfrage vom Http-Server bearbeitet und die Passwortkontrolle an den Datenbank-Teil übergeben per send
:
JsonObject request = new JsonObject().put("name", name) .put("passwort", passwort); DeliveryOptions options = new DeliveryOptions() .addHeader("action", "ueberpruefe-passwort"); vertx.eventBus().send(EB_ADRESSE, request, options, reply -> { if (reply.succeeded()) { JsonObject body = (JsonObject) reply.result().body(); if (body.getBoolean("passwortStimmt") == true) { session.put("angemeldet", "ja"); jo.put("typ", "überprüfung").put("text", "ok"); } else { jo.put("typ", "überprüfung").put("text", "nein"); } response.end(Json.encodePrettily(jo)); } else { jo.put("typ", "überprüfung").put("text", "nein"); response.end(Json.encodePrettily(jo)); } });
Java
Beim Empfänger muss festgelegt werden, dass dort Nachrichten an die gewünschte Adresse angenommen werden:
vertx.eventBus().consumer(EB_ADRESSE, this::onMessage);
Java
In der entsprechenden Methode onMessage
werden die Nachrichten verarbeitet und beantwortet:
private void onMessage(Message<JsonObject> message) { String action = message.headers().get("action"); if (action.equals("ueberpruefe-passwort")){ String name = message.body().getString("name"); String passwort = message.body().getString("passwort"); if (passwort.equals("geheim")&&name.equals("user")){ message.reply(new JsonObject().put("passwortStimmt", Boolean.TRUE)); } else { message.reply(new JsonObject().put("passwortStimmt", Boolean.FALSE)); } } }
Java