Code-Blog von redelin-soft.de

"Wissen durch Ausprobieren" ist meine Devise.
Viele Probleme lassen sich auf die vielfältigsten Weisen lösen und sicherlich schlage ich mit den Einträgen in diesem Blog nicht immer den "richtigsten" Weg ein.
Vielmehr möchte ich Ideen und Denkanstöße in verschiedenen Sprachen und Techniken vermitteln, sowie gerne zu Diskussionen anregen.
Helfen euch Beiträge von hier weiter oder habt ihr grobe Fehler gefunden, teilt mir dieses gerne über das Kontaktformular mit.

Training: Knacke das Zahlenschloss

Hast du Lust zu erfahren, was hinter einer Webseite steckt?
Worin bestehen Gemeinsamkeiten eines Zahlenschlosses in der realen und digitalen Welt?
Mit diesen Fragen beschäftigen wir uns in diesem Training.
Wenn du mehr Interesse hast HTML, CSS und vor allem JavaScript zu lernen, besuche das Training.

Kafka@Docker - eine erste Verwendung mit Spring Cloud Streaming

In Zeiten der Microservice-Architekturen wird es immer schwieriger den Überblick über die Informationsflüsse zu behalten.
Online Verarbeitungen können über REST Schnittstellen zu Kopplungen von Systemen verschiedener Verantwortlicher Teams führen, was zu fragilen Prozessen führt.

lose Kopplung, Orchestrierung, Event-getriebene Prozesse

Diese Schlagwörter können das beschriebene Problem lösen und ganz neue Möglichkeiten eröffnen.
lose Kopplung
von Services kann bspw. mit einer Service Discovery wie Netflix Eureka erreicht werden.
Orchestrierung
von Services mit Mesos und DC/OS, oder Kubernetes und OpenShift, sowie DockerSwarm sind bekannte und vielfach verwendete Plattformen.
Event-getriebene Prozesse
mit Messaging Systemen wie RabbitMQ, ActiveMQ, SonicMQ oder Apache Kafka.

Vor lauter Möglichkeiten verliert man dabei schnell das eigentliche Ziel aus den Augen und verliert sich womöglich in architektonischen Schönheiten und Dilemma Fragestellungen.

In diesem Beitrag soll es um eine konkrete Problemstellung gehen, die nachfolgend in einer Gherkin Notation beschrieben ist. Dabei dienen mehrere Szenarien zur Darstellung der Anforderung

Die Anforderung

Wenn der Webaufruf
http://localhost:8080/source/forward?message=HelloWorld
gesendet wird
Dann empfängt ein Prozess diese Nachricht
Und leitet sie an einen Prozessor weiter
Und dieser gibt die Nachricht aus


Gegeben sei der Zustand des Prozessors soll keinen Einfluss auf das Empfangen von Nachrichten haben
Wenn der Webaufruf
http://localhost:8080/source/forward?message=HelloWorld
gesendet wird
Dann empfängt ein Prozess diese Nachricht
Und leitet sie an einen Prozessor weiter
Und dieser gibt nichts aus, da er abgeschaltet ist

Die Architektur

Aus der ersten Anforderung leitet sich ein kleiner REST Service mit einer Request Mathode ab.
Die zweite Anforderung relativiert dieses direkt und splittet den Prozess implizit in zwei entkoppelte Stufen.
Wir müssen also in Empfangen von Nachrichten und Verarbeiten von Nachrichten unterscheiden.
Um diese Unterscheidung deutlicher darzustellen, separieren wir direkt in zwei Services.
Damit nun die Datenquelle mit dem Prozessor Informationen austauschen kann, bedarf es einem Zwischenspeicher.
Gemäß der Anforderung würde eine einfache Datenbank denkbar sein, aber auch Queue Systeme sind im Lösungsraum enthalten und deutlich passender als eine Datenbank. Dieser Bericht dreht sich um Kafka, also entscheiden wir uns auch für Kafka als Messaging Service. Art und Frequenz der Datenüberaging sind dabei vorerst irrelevante Parameter.

Was ist Kafka?

Kafka ist ein hoch performanter Message Streaming Dienst von Apache, welcher neben einfachem Messaging ebenfalls komplexes Streaming ermöglicht.
Da man mit der Kafka Architektur und Konfiguration ganze Bücher füllen kann, verweise ich für den Beginn auf Kafka in a nutshell .
Konkrete Implementierungsdetails können der am Ende angegebenen Code Referenz entnommen werden.

Source, Process, Sink

Kafka bildet Nachrichten / Events in Prozessketten ab.
  • Source ist der Daten-erzeugende Prozessschritt. Also der Schritt, der das Event in Kafka auslöst.

  • Process(es) kann es beliebig viele geben. Sie reagieren auf Events im Stream und verändern / erweitern diese.

  • Sink nimmt final das Event an und sichert den Status. Dieses kann ein Persistieren, als auch gleichzeitig die Source einer neuen Prozesskette sein.

Let's code...

startup kafka

Als Messaging-Dienst sollte Kafka (benötigt immer Zookeeper) immer mind. als 3er Gespann aufgebaut werden. So können über die Instanzen Repliaktionen ausfallsicher verteilt werden.
Für unseren Test verzichten wir auf Ausfallsicherheit sowie Sicherheitsmaßnahmen wie Authentifizierung und Verschlüsselung.
In der Code Referenz begeben wir uns in das Verzeichnis
kafka
.
  1. #zuerst modifiziere die docker-compose.yml und für deine HOST IP als KAFKA_ADVERTISED_HOST_NAME ein
  2. docker-compose up -d

Durch den Befehl
docker ps -a
sehen wir nun zwei gestartete Container (kafka und seinen Zookeeper)
  1. CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
  2. 726f03575ff3 sredelin/kafka:scala-2.12-kafka-1.0.0 "start-kafka.sh" 9 hours ago Up 9 hours 0.0.0.0:9092->9092/tcp kafka_kafka_1
  3. d4e0554c04ce sredelin/zookeeper:3.4.11 "/usr/bin/start.sh" 9 hours ago Up 9 hours 2888/tcp, 0.0.0.0:2181->2181/tcp, 3888/tcp kafka_zookeeper_1

Mittels
docker logs -f kafka_kafka_1
sollte in den letzten 10-15 Zeilen ersichtlich sein, dass die beiden Testtopics angelegt wurden.

Die Datenquelle (Source)

Eine Datenquelle erzeugt Daten. In unserem Fall ist dieses die SpringBoot Applikation im Verzeichnis
SpringDataFlowSource
.
Das Gradle Projekt kann bspw. für Eclipse mit dem vorhandenen Wrapper in Unix einfach mit
./gradlew eclipse
aufgebaut werden.
Gestartet wird die Application mit
./gradlew bootRun
und ist danach über http://localhost:8080/source/forward?message=HelloWorld erreichbar.

Durch den Aufruf des genannten Pfades werden im
de.redelin.Controller
Nachrichten für zwei Prozessketten 10-fach dupliziert erzeugt.
Zwei Producer
de.redelin.DataProducer
und
de.redelin.AnotherDataProducer
senden dabei Nachrichten für zwei verschiedene Topics.
Die Producer selbst sind fast identisch, nur ihre Basis unterscheidet sich entscheidend.
  1. @EnableBinding(Source.class)
  2. public class DataProducer {
  3.   final static Logger LOG = LoggerFactory.getLogger(DataProducer.class);
  4.   @Autowired
  5.   Source source;
  6.   public void sendMessage(HolyMessage message) {
  7.     LOG.info("Log message: " + message.getText());
  8.     source.output().send(MessageBuilder.withPayload(message).build());
  9.   }
  10. }

Das Binding an das Interface
org.springframework.cloud.stream.messaging.Source
zeigt uns den Weg zur Konfiguration.
  1. public interface Source {
  2.   String OUTPUT = "output";
  3.   @Output(Source.OUTPUT)
  4.   MessageChannel output();
  5. }

Das Interface
Source
definiert also nur, dass es sich um eine Datenausgabe (Annotation
@Output
) mit einem konstanten String "output" handelt.
Vergleichen wir dazu die
application.yml
des Projektes sehen wir diese Konfiguration wieder.
Die Anwendung des Interfaces zeigt Spring Cloud Data Flow also welches Kafka Topic mit welchen Einstellungen für welchen Zweck verwendet werden soll.
  1. spring:
  2.   application:
  3.     name: source # source is at the beginning of a process (source -> processor -> sink || source -> sink)
  4.   cloud:
  5.     stream:
  6.       bindings:
  7.         output: # topic-binding one based on Source.class
  8.           binder: kafka
  9.           destination: testOne # name of topic to sent with
  10.         anotheroutput: #topic-binding two based on AnotherSource.class
  11.           binder: kafka
  12.           destination: testTwo # name of topic to sent with

Die Datensenke (Sink)

Um die Funktionsweise kürzer zu beschreiben, verzichten wir auf die Ausprägung
Process
und bilden nur eine Prozesskette in Minimalkonfiguration mit
Source
und
Sink
ab.
Im Gradle Projekt
SpringDataFlowProcess
sollen die Daten angenommen und abschließend verarbeitet werden.
Die Klasse
de.redelin.DataConsumer
zeigt, wie der Datenempfang konfiguriert wird. Wie bei der Source sehen wir hier eine Verwendung von Binding-Annotationen.
  1. @EnableBinding(Sink.class)
  2. public class DataConsumer {
  3.   final static Logger LOG = LoggerFactory.getLogger(DataConsumer.class);
  4.   @ServiceActivator(inputChannel = Sink.INPUT)
  5.   public void receiveMessage(final HolyMessage message) {
  6.     LOG.info(String.format("Message is: %s with %d for direction %s", message.getText(), message.getId(),
  7.       message.getDirectionKey()));
  8.     /*
  9.      * message with id 2 should not be processable, so we can see an error
  10.      * message in processing and how kafka and spring cloud data flow react.
  11.      * Exception-Handling is bad in this implementation, because normally as
  12.      * example an error-topic should be used.
  13.      */
  14.     if (message.getId() == 2) {
  15.       LOG.error("ID 2 not legal!");
  16.       throw new IllegalArgumentException("My Exception");
  17.     }
  18.   }
  19. }

Schaut man sich das Interface
org.springframework.cloud.stream.messaging.Sink
an, sieht man, dass dieses mit
@Input
annotiert und einem fixen String "input" versehen ist.
Auch diese Referenz finden wir in der
application.yml
wieder.
  1. spring:
  2.   application:
  3.     name: sink # sink is at the end of a process (source -> processor -> sink || source -> sink)
  4.   cloud:
  5.     stream:
  6.       bindings:
  7.         input: # topic-binding one based on Sink.class
  8.           binder: kafka
  9.           destination: testOne # name of topic to receive from
  10.           group: groupA # group has its own offset in kafka
  11.         anotherinput: #topic-binding two based on AnotherSink.class
  12.           binder: kafka
  13.           destination: testTwo # name of topic to receive from
  14.           group: groupB # group has its own offset in kafka

Wir sehen hier, dass Kafka pro Topic und Gruppe Offsets definiert, wodurch wir sehr einfach einstellen können, ob mehrere Prozesse parallel Daten verarbeiten oder verschiedene Prozesse die gleichen Daten interpretieren sollen. Fällt in dieser Kette der Prozess "Datenempfänger" aus und startet später wieder, erhält er alle Daten, die seit der letzten Übertragung neu hinzugekommen sind.

Ausprobieren und Testen hilft dem Verständnis

Probiere diesen kleinen Einblick in die Verarbeitung von Datenströmen und asynchronen Kommunikationen gerne aus. Es gibt unzählige Anwendungsszenarien und sicherlich nicht immer den EINEN Weg das Ziel zu erreichen.
Wir haben während unseres Ausfluges in diese Welt jedenfalls noch folgende wichtige Dinge festgestellt.
  • Ohne Gruppennamen in den Datenempfängern ist der Offset immer
    latest
    beim Starten

  • Es muss zwingend ein "Retry" Prozess implementiert werden, da Kafka im Gegensatz zu Queues ein Stream ist und fehlerhafte Nachrichten nach mehrmaligem Versuch einfach überlesen werden. (siehe Retry operations

  • Alte Nachrichten mit Objekten, die nicht zu neuen Consumern passen, führen zu extremen Problemen für neue UserGroups.

  • Doppelverarbeitung von Nachrichten bei mehreren Consumern der gleichen userGroup ist durch Kafka ausgeschlossen

  • Mounte das Docker-Verzeichnis
    /kafka
    in ein
    docker volume
    und du wirst keine Datenverluste beim Neustart deiner Instanzen haben

Code Referenz

https://bitbucket.org/firefighter-97/kafka/src

PHP :: PDFs erstellen und verwalten

Die Erstellung von PDFs kann einem gewaltige Kopfschmerzen bereiten und es dauert sehr lange bis man ein sinnvolles und sehenswertes Ergebnis erhält.
Zum Glück gibt es seit vielen Jahren großartige OpenSource (quelloffene) Communities, die fast für jedes Problem eine hilfreiche Bibliothek erarbeitet haben.

Die Anforderung im nachfolgenden Beispiel lautet in der Gherkin-Notation wie folgt:
Gegeben sei ein beliebiger HTML Code mit einem tabellarischen Aufbau
Wenn dieser HTML Code in eine PDF Druckklasse gegeben wird
Dann wird dieser Code im PDF korrekt angezeigt
Und das PDF direkt ausgegeben.


Zur Hilfe nehmen wir eine Bibliothek namen TCPDF, die unter dem nachfolgenden Link bezogen werden kann.
https://github.com/tecnickcom/tc-lib-pdf

Um nun die Funktionalität noch weiter zu kapseln verwenden wir für den Anfang eine eigene Klasse, die einen statischen Methodenaufruf implementiert.

  1. <?php
  2. class PDFPrinter {
  3.   public static function writePDF($pdfAuthor, $title, $subject, $html, $pdfName){
  4.     // TCPDF Library laden
  5.     require_once(__DIR__ .'/externals/tcpdf/tcpdf.php');
  6.     
  7.     // Erstellung des PDF Dokuments
  8.     $pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
  9.     
  10.     // Dokumenteninformationen
  11.     $pdf->SetCreator(PDF_CREATOR);
  12.     $pdf->SetAuthor($pdfAuthor);
  13.     $pdf->SetTitle($title);
  14.     $pdf->SetSubject($subject);
  15.     
  16.     
  17.     // Header und Footer Informationen
  18.     $pdf->setHeaderFont(Array(PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN));
  19.     $pdf->setFooterFont(Array(PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA));
  20.     
  21.     // Auswahl des Font
  22.     $pdf->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED);
  23.     
  24.     // Auswahl der MArgins
  25.     $pdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT);
  26.     $pdf->SetHeaderMargin(PDF_MARGIN_HEADER);
  27.     $pdf->SetFooterMargin(PDF_MARGIN_FOOTER);
  28.     
  29.     // Automatisches Autobreak der Seiten
  30.     $pdf->SetAutoPageBreak(TRUE, PDF_MARGIN_BOTTOM);
  31.     
  32.     // Image Scale
  33.     $pdf->setImageScale(PDF_IMAGE_SCALE_RATIO);
  34.     
  35.     // Schriftart
  36.     $pdf->SetFont('freesans', '', 10);
  37.     
  38.     // Neue Seite
  39.     $pdf->AddPage();
  40.     
  41.     // Fügt den HTML Code in das PDF Dokument ein
  42.     $pdf->writeHTML($html, true, false, true, false, '');
  43.     
  44.     //Ausgabe der PDF
  45.     
  46.     //Variante 1: PDF direkt an den Benutzer senden:
  47.     $pdf->Output($pdfName, 'I');
  48.     
  49.     //Variante 2: PDF im Verzeichnis abspeichern:
  50.     //$pdf->Output(__DIR__.'/output/pdf/'.$pdfName, 'F');
  51.     //echo 'PDF herunterladen: <a href="'.$pdfName.'">'.$pdfName.'</a>';
  52.   }
  53. }

Anschließend können wir mit dem nachfolgenden Funktionsaufruf ein statisches Beispiel-PDF als direkten Download in der Browser-Session erzeugen.
  1. <?php
  2. $html = "<h1>Überschrift im PDf</h1>
  3.     <table>
  4.     <tr>
  5.       <td>Spalte 1</td>
  6.       <td>Spalte 2</td>
  7.     </tr>
  8.     <tr>
  9.       <td>Wert 1</td>
  10.       <td>Wert 2</td>
  11.     </tr>
  12.     </table>";
  13. $pdfAuthor = "Sven";
  14. $title = "mein pdf from redelin-soft.de";
  15. $subject = "ein kleiner Titel";
  16. $pdfName = "example.pdf";

  17. require_once __DIR__ . '/pdfprinter.class.php';
  18. PDFPrinter::writePDF($pdfAuthor, $title, $subject, $html, $pdfName);