{"action":"created","comment":{"url":"https://api.github.com/repos/adessoAG/devblog/pulls/comments/227659224","pull_request_review_id":167750161,"id":227659224,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIyNzY1OTIyNA==","diff_hunk":"@@ -0,0 +1,497 @@\n+---\n+layout: [post, post-xml]\n+title:  \"Apache Kafka als Streaming Plattform\"\n+date:   2018-11-02 08:00\n+author: swindisch\n+categories: [Architektur, Java]\n+tags: [Microservice, Message-Queue]\n+---\n+In der heutigen Welt wird es immer wichtiger, Mengen verschiedener Informationen zu sammeln,\n+auszuwerten und an unterschiedliche Konsumenten zu verteilen.\n+Zusätzlich müssen Firmen möglichst schnell auf Marktsituationen reagieren und ihre Software-Systeme an diese anpassen.\n+Damit die Software-Systeme nicht in eine zu starke Abhängigkeit geraten,\n+wird die Architektur dieser Systeme oft als eine verteilte heterogene Landschaft\n+mit unterschiedlicher fachspezifischen Technologien umgesetzt,\n+deren Schnittstellen meist nur noch über den synchronen oder asynchrone Austausch von Nachrichten lose miteinander verbunden sind.\n+In diesem Blog wird der Einsatz von Kafka als Message-Queue und Streaming-Plattform erläutert.\n+\n+# Nachrichtenorientierte Systeme, Message Oriented Middleware (MOM)\n+\n+Zum Austausch der Nachrichten werden sogenannte Nachrichtenorientierte Systeme, Message Oriented Middleware (MOM), eingesetzt,\n+die als eigenständige Produkte am Markt existieren.\n+\n+Bei Nachrichtenorientierten Systemen wird zwischen folgenden Protokollen unterschieden.\n+* Message Passing   \t: Dies ist die direkte Kommunikation zwischen zwei Systemen.\n+* Message Queueing\t    : Dies ist die indirekte Kommunikation über eine Middleware in Form einer Warteschlange.\n+* Publish & Subscribe   : Hier stellt ein Herausgeber dem Konsumenten Nachrichten zur Verfügung.\n+Der Austausch kann dabei asynchron oder synchron erfolgen.\n+\n+Kafka ist eine solches Nachrichtenorientiertes System, das Daten in Form von Nachrichten zwischen verschiedenen Systemen austauscht.\n+Es wurde ursprünglich von LinkedIn entwickelt, dann aber an die Apache Software Foundation übergeben.\n+\n+Kafka wird in der Rolle des Publish & Subscribers implementiert und genutzt.\n+Es wird dabei als Middelware zwischen Systemen genutzt, ohne dass sich die Systeme kennen.\n+Der Erzeuger der Nachricht ist in der Rolle Herausgeber (Publisher) dafür verantwortlich seine Nachrichten an Kafka zu übergeben.\n+Der Konsument (Subscriber) ist dafür verantwortlich, die Nachrichten entgegen zu nehmen.\n+\n+Herausgeber und Konsument kennen sich nicht. Sie kommunizieren über einen definierten Kanal in Kafka.\n+Da Kafka dann als zentrales System zum Austausch von Nachrichten dient, muss es stabil, hochverfügbar und belastbar aufgesetzt werden.\n+Diesen Ansprüchen genügt Kafka durch Grundlegende Architekturentscheidungen, wie beispielsweise einen hohen Daten-Durchsatz\n+oder auch der Replikation der Kafka Nodes, um Ausfälle zu kompensieren.\n+Kafka wurde mit dem Gedanken an BigData und Streaming geplant.\n+\n+In typischen Nachrichten-Systemen werden Nachrichten gelöscht, wenn sie gelesen wurden.\n+Kafka speichert die Nachrichten allerdings dauerhaft, um eine Nachrichtenhistorie abbilden zu können.\n+Damit können beispielsweise Event-Sourcing Systeme mit Kafka abgebildet werden, ohne dass die angrenzenden Systeme dies selbst implementieren müssen.\n+\n+Zusammengefasst ist Kafka eine Nachrichtenorientierte Publish & Subscribe Open-Source Software\n+basierend auf einer verteilten, partitionier- und verteilbaren Architektur.\n+\n+# Einsatzgebiete\n+\n+Der Einsatz von Nachrichtenorientierten System kann in folgende Gebiete unterteilt werden.\n+* Nachrichten-Übermittlung (Messaging)\n+  * Micro-Services nutzen eine MOM um Nachrichten und Events untereinander auszutauschen.\n+* Website Activity Tracking\n+  * Die Bedienung einer Web-Anwendung wird detailliert in Protokollen gespeichert,\n+    in Kafka gespeichert, verarbeitet und wieder verteilt.\n+    Dies war auch die ursprüngliche Anforderung für die Erschaffung von Kafka\n+* Log Aggregation\n+  * Unterschiedliche Quellen und Formate von Protokoll-Dateien werden massenhaft von der MOM gesammelt,\n+    zentral gespeichert, in ein einheitliches Format überführt und zur Auswertung zur Verfügung gestellt.\n+* Stream Processing\n+  * Mehrfache und parallele Bearbeitung von Datenströmen zur Erweiterung oder Transformierung. Dabei werden die Datenströme durch Kafka Topics geschoben.\n+* Event Sourcing\n+  * Event Sourcing ist ein Enterprise Architecture Pattern, bei dem Veränderungen von Daten bzw. Objekten\n+    als Event-Folge gespeichert werden.\n+    Dadurch ist es jederzeit möglich den Zustand zu einem bestimmten Zeitpunkt durch das Einlesen der Event-Folge zu ermitteln.\n+* Real-Time Processing\n+  * Empfänger, die Echtzeit-Verarbeitung ermöglichen, wie beispielsweise Apache Spark oder Storm.\n+\n+Bekannte Unternehmen, die Kafka einsetzen sind beispielsweise: LinkedIn, Yahoo, Twitter, Netflix, Square, FourSquare, Pinterest, Spotify, Uber, AirBnB, Tumblr.\n+\n+# Architektur\n+\n+Das folgende Kapitel beschreibt die grundlegende Architektur und die Bestandteile von Kafka.\n+\n+Da sich in der Software-Welt oft die englischen Begriffe als Standard genutzt werden,\n+werden hier ebenfalls die englischen Begriffe verwendet und nur bei Bedarf deutsche Synonyme verwendet bzw. darauf hingewiesen.\n+\n+## Records / Nachrichten\n+\n+Kafka speichert die Nachrichten, genannt Records, als einfachen Wert ab.\n+Dabei wird der Inhalt nicht betrachtet. Das Format der Nachricht wird vom Ersteller bestimmt (JSON, XML).\n+Zusätzlich wird die Nachricht mit einem Schlüssel und einem Zeitstempel gespeichert.\n+\n+Nachrichten werden nach dem Lesen nicht gelöscht, sondern dauerhaft aufbewahrt.\n+Allerdings kann konfigurativ bestimmt werden, dass eine Nachricht nach Ablauf einer bestimmten Zeit (Stunden / Tage) gelöscht wird.\n+\n+## Topics\n+\n+Records werden in Kafka in Topics kategorisiert. Fachlich getrennte Themen können so in eigenen Topics getrennt abgelegt werden.\n+Eine Topic kann am ehesten mit einem Order oder einem Ablagefach verglichen werden, in den gleichwertige Dokumente abgelegt werden.\n+\n+## Partitionen\n+\n+Ein Topic wird in Partitionen unterteilt. Neue Records werden an Partitionen gehangen.\n+Für jeden Konsumenten speichert Kafka die Position des zuletzt gelesenen Records ab,\n+wodurch für die Konsumenten immer erkenntlich bleibt, welcher Records zuletzt gelesen wurde.\n+Der Konsument muss das Lesen des Records an Kafka bestätigen, einen sogenannten Commit ausführen.\n+Dadurch kann Kafka die Position des Konsumenten speichern.\n+\n+Es ist möglich eine Liste von Records in einem Commit zu bestätigen.\n+Dies beschleunigt die Bearbeitung, birgt aber auch die Gefahr, dass bei einem Fehler während einer Verarbeitung,\n+nochmals die ganze Liste gelesen und bestätigt werden muss.\n+\n+Wenn für ein Topic mehrere Partitionen angelegt werden, so wird der Hash des Record-Keys zur Auswahl der Partitionen genommen.\n+Es ist aber auch möglich eine eigene Berechnungsfunktion zu hinterlegen.\n+Dadurch können die Records auf beliebig viele Partitionen verteilt werden,\n+was die gleichzeitige Bearbeitung der Daten durch viele Konsumenten ermöglicht.\n+\n+Die Reihenfolge innerhalb einer Partition bleibt zwischen dem Schreiben und Lesen erhalten.\n+D.h. die Nachrichten können in der Reihenfolge vom Konsumenten gelesen werden, wie der Hersteller sie eingestellt hat.\n+\n+Das Anhängen von Records an die Partitionen stellt eine sehr effiziente Operation statt, die auf klassischen Datenträgern (HDD, SDD) stattfinden kann.\n+Da Partitionen über Kafka-Server verteilt werden, kann eine gewünschte Skalierbarkeit und Redundanz sichergestellt werden.\n+\n+## Push-Verfahren\n+\n+Bei Nachrichten-Middleware wird zwischen einem Push- und Pull-Verfahren unterschieden.\n+Bei dem Push-Verfahren sorgt die Middleware dafür, dass der Konsument die neue Nachricht übermittelt bekommt,\n+während beim Pull-Verfahren der Konsument die Middleware zyklisch anfragen muss, ob eine neue Nachricht vorliegt.\n+\n+Kafka wird im Pull-Verfahren betrieben. Dies hat den Vorteil, dass der Konsument selbst entscheidet,\n+wann er in der Lage ist, eine neue Nachricht zu verarbeiten. Beim Push-Verfahren kann es bei Lastspitzen zu einer Überflutung der Empfänger kommen.\n+\n+## Replikation\n+\n+Kafka bietet die Möglichkeit eine Nachricht auf mehrere Replikationen zu verteilen.\n+Dadurch wird eine hohe Ausfallsicherheit garantiert. Bei n Replikationen können bis zu n-1 Broker ausfallen,\n+ohne das die Nachricht nicht mehr abgerufen werden kann.\n+\n+Die Anzahl der Replikationen wird pro Topic definiert. Dabei fungiert eine Replika als führend, während alle anderen die Folge-Replikas sind.\n+\n+Kafka unterscheidet zwischen synchroner und asynchroner Replikation:\n+* Bei der synchronen Replikation sendet der Erzeuger die Nachricht an Kafka.\n+  Die Nachricht wird dann in der führenden Replika gespeichert. Jede Folge-Replika holt sich die Nachricht vom System,\n+  speichert diese und sendet den Status zurück. Erst wenn alle Replikationen gespeichert wurden, wird der Erzeuger darüber informiert.\n+* Bei der asynchronen Replikation erhält der Erzeuger direkt nachdem die führende Replika die Nachricht gespeichert hat, die Bestätigung.\n+  Der Rest läuft anschließend im Hintergrund ab.\n+\n+## Broker\n+\n+Ein Kafka Server wird Broker genannt. Der Server empfängt Nachrichten von Erzeugern und speichert diese in seinem Speicherbereich.\n+Er liefert die Nachrichten an die Konsumenten, wenn diese sie anfragen. Durch die Kafka Architektur ist es,\n+abhängig der gewählten Infrastruktur, bereits möglich, dass ein Broker mit sehr vielen Partitionen und Nachrichten umgehen kann.\n+\n+Bei weiteren Mengen- bzw. Performance-Anforderungen können mehrere Broker als ein Cluster zusammengeschaltet werden,\n+so dass dann Partition und Nachrichten auf verschiedene Broker verteilt und repliziert werden.\n+\n+## Schnittstellen\n+\n+Um Kafka mit Nachrichten zu füllen bzw. diese wieder aus Kafka zu lesen,\n+gibt es die Möglichkeit dies über die bekannten Ersteller- und Konsumenten-Schnittstellen (Producer- / Consumer-API)\n+zu realisieren oder aber das Framwork Kafka Connect zu nutzen.\n+\n+Die Producer- und Consumer-API wird genutzt, wenn die umliegenden Anwendungen selbst entwickelt werden,\n+und so Zugriff auf die Erstellung bzw. Bearbeitung der Nachrichten möglich sind.\n+\n+Das Framwork Kafka Connect wird genutzt, um Datenspeicher anzusprechen,\n+die von einem Dritthersteller sind (Datastore, Datenbank, Big-Data-Systeme).\n+Das Framwork besteht aus einer API und einer Runtime-Umgebung, um die entwickelten Konnektoren als Plugins auszuführen.\n+Beispielsweise kann Kafka Connect genutzt werden, um Daten aus MySQL-Datenbanken laden und in ElasticSearch zu speichern.\n+\n+![Architektur von Kafka](/assets/images/posts/kafka_streaming/Kafka-Architektur.png)\n+\n+# Installation\n+\n+Kafka kann für verschiedene Datenmengen unterschiedlich aufgesetzt werden.\n+Beispielsweise wird es als ein Cluster installiert, der entweder auf einem Server mit nur einem Broker aufgesetzt wird.\n+Oder aber verteilt auf mehreren Servern mit mehreren Brokern.\n+\n+Der Cluster setzt sich dann aus den folgenden Themen zusammen:\n+\n+* Zookeeper\t: Zur zentralen Koordinierung der Konfiguration und Systeme\n+* Broker\t: Kafka Prozess, verteilt auf einem oder mehrere Server\n+* Topic\t\t: Topic zur Sammlung der Nachrichten\n+* Ersteller\t: Das System, das Nachrichten in Kafka ablegt\n+* Konsument\t: Das System, das Nachrichten aus Kafka abholt\n+\n+Für ein einfaches Beispiel nutzen wir Kafka in einer Windows-Umgebung mit einem bereits installierten Java Framework.\n+\n+![Java Version](/assets/images/posts/kafka_streaming/java.png)\n+\n+Kafka kann von der Apache Seite heruntergeladen werden. Die Aktuelle Version ist hier erreichbar:\n+[Kafka Download](https://kafka.apache.org/downloads \"Kafka Download\")\n+\n+Das genutzte heruntergeladene Archiv `kafka_2.11-2.0.0.tgz` wird in das Verzeichnis `c:\\dev` entpackt, so dass die folgende Verzeichnis-Struktur entsteht:\n+\n+![Kafka Verzeichnis](/assets/images/posts/kafka_streaming/kafka_folder.png)\n+\n+In dem Unterverzeichnis `c:\\dev\\kafka_2.11-2.0.0\\bin\\windows` liegen die Batch-Dateien für die Windows-Umgebung.\n+\n+Kafka nutzt Apache Zookepper als zentralen Dienst für Konfigurationen und verteilte Verwaltung, so dass dieser zuerst gestartet werden muss:\n+`zookeeper-server-start.bat ..\\..\\config\\zookeeper.properties`\n+\n+![ZooKeeper](/assets/images/posts/kafka_streaming/zookeeper.png)\n+\n+Im Anschluss wird der Kafka Broker gestartet:\n+\n+`kafka-server-start.bat ..\\..\\config\\server.properties`\n+\n+![Kafka](/assets/images/posts/kafka_streaming/kafka.png)\n+\n+Die Konfigurationsdateien sind mit Default-Werten belegt, so dass dieses einfache Beispiel ohne viel Aufwand gestartet werden kann.\n+\n+# Konfiguration\n+\n+Nachdem Kafka installiert und gestartet wurde, kann das System nun direkt genutzt werden.\n+Als ersten Schritt müssen die Topics angelegt werden, damit hier die später die Nachrichten (Records) gespeichert werden können.\n+\n+`kafka-topics.bat --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic Kunde1`\n+\n+Mit dem Kommando wird die Topic mit Namen `Kunde1` angelegt und nur mit einer Replikation und Partition definiert.\n+Die Anzahl der Replikationen wird durch die Anzahl der gestarteten Broker beschränkt, während die Anzahl der Partitionen beliebig angegeben werden kann.\n+Eine Liste aller bereits erstellten Topics wird mit `kafka-topics.bat --list --zookeeper localhost:2181` abgefragt.\n+\n+Um eine Konfiguration zu testen gibt es weitere Kommandos, die auf der Konsole ausgeführt werden können.\n+Eine Liste der Befehle kann über die offizielle Dokumentation erreicht werden.\n+\n+Nachrichten können mit diesem Kommando erstellt werden:\n+\n+`kafka-console-producer.bat --broker-list localhost:9092 --topic Kunde1`\n+\n+Dabei wird ein Stream geöffnet, mit der Nachrichten pro Zeile eingegeben werden können.\n+\n+![Producer](/assets/images/posts/kafka_streaming/producer.png)\n+\n+Nachrichten können mit diesem Kommando abgerufen werden.\n+\n+`kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic Kunde1  --from-beginning`\n+\n+# Code-Beispiele\n+\n+Das folgende sehr einfache Beispiel zeigt die Erstellung von 100 Testnachrichten, die an die Kafka Topic `Kunde1` übergeben werden.\n+Zunächst werden die Konfiguration zum Kafka-System und die Methode der Daten-Serialisierung angegeben.\n+Innerhalb der Schleife wird im Anschluss jeweils ein Record erstellt und an das Producer-Objekt übergeben.\n+Dabei wird die Topic, der Nachrichten-Schlüssel und –Text mitgegeben.\n+Falls die Nachricht nicht als Text mitgegeben werden soll, muss ein passender Serialisierer angegeben bzw. entwickelt werden.\n+\n+``` java\n+\n+public class SimpleProducer\n+{\n+    public static void main(String[] args)\n+    {\n+        Properties properties = new Properties();\n+        properties.put(\"bootstrap.servers\", \"localhost:9092\");\n+        properties.put(\"key.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\");\n+        properties.put(\"value.serializer\", \"org.apache.kafka.common.serialization.StringSerializer\");\n+\n+        KafkaProducer kafkaProducer = new KafkaProducer(properties);\n+\n+        for(int i = 1; i <= 100; i++)\n+        {\n+            System.out.println(String.format(\"Test Nachricht - %03d\", i));\n+            kafkaProducer.send(new ProducerRecord(\"Kunde1\", Integer.toString(i), String.format(\"Test Nachricht - %03d\", i)));\n+        }\n+        kafkaProducer.close();\n+    }\n+}\n+\n+```\n+\n+Das folgende sehr einfache Beispiel zeigt das Konsumieren von Testnachrichten, aus der Kafka Topic `Kunde1`.\n+Zunächst werden die Konfiguration zum Kafka-System und die Methode der Daten-Serialisierung angegeben.\n+Zusätzlich wird die Consumer-Gruppe und Consumer-id übergeben und bestimmt, dass das Commit automatisch durchgeführt werden soll.\n+Anschließend wird die Liste der Kafka-Topics angegeben, die für diesen Konsumenten von Interesse sind.\n+Innerhalb der while-Schleife wird eine Liste der neuen Nachrichten abgefragt und in eine lokale Liste geschrieben.\n+Innerhalb der for-Schleife wird dann jeder Eintrag der Liste auf der Konsole ausgegeben, mit dem Offset, dem Schlüssel und dem Wert.\n+\n+``` java\n+\n+public class SimpleConsumer\n+{\n+    public static void main(String[] args)\n+    {\n+        String topic = \"Kunde1\";\n+        Properties properties = new Properties();\n+        properties.put(\"bootstrap.servers\", \"localhost:9092\");\n+        properties.put(\"group.id\", \"Kunde1_Gruppe\");\n+        properties.put(\"enable.auto.commit\", \"true\");\n+        properties.put(\"auto.commit.interval.ms\", \"1000\");\n+        properties.put(\"key.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\");\n+        properties.put(\"value.deserializer\", \"org.apache.kafka.common.serialization.StringDeserializer\");\n+\n+        KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<String, String>(properties);\n+        kafkaConsumer.subscribe(Arrays.asList(topic));\n+\n+        System.out.println(String.format(\"Konsumiere Daten aus Topic: '%s'\", topic));\n+\n+        boolean running = true;\n+        int count = 0;\n+        try\n+        {\n+            while(running)\n+            {\n+                ConsumerRecords<String, String> records = kafkaConsumer.poll(100);\n+\n+                for (ConsumerRecord<String, String> record : records)\n+                {\n+                    System.out.println(String.format(\"Partition = '%s' - Offset = '%s' - Schluessel = '%s' - Wert = '%s'\",record.partition(), record.offset(), record.key(), record.value()));","path":"_posts/2018-11-02-kafka-streaming.md","position":null,"original_position":301,"commit_id":"35b5146a6843f90078fbcee238b901d7ed858c34","original_commit_id":"c9d7432bb9caa842a40de2a2bcdc541638fdd61c","user":{"login":"swindisch","id":11214403,"node_id":"MDQ6VXNlcjExMjE0NDAz","avatar_url":"https://avatars0.githubusercontent.com/u/11214403?v=4","gravatar_id":"","url":"https://api.github.com/users/swindisch","html_url":"https://github.com/swindisch","followers_url":"https://api.github.com/users/swindisch/followers","following_url":"https://api.github.com/users/swindisch/following{/other_user}","gists_url":"https://api.github.com/users/swindisch/gists{/gist_id}","starred_url":"https://api.github.com/users/swindisch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/swindisch/subscriptions","organizations_url":"https://api.github.com/users/swindisch/orgs","repos_url":"https://api.github.com/users/swindisch/repos","events_url":"https://api.github.com/users/swindisch/events{/privacy}","received_events_url":"https://api.github.com/users/swindisch/received_events","type":"User","site_admin":false},"body":"done","created_at":"2018-10-24T06:44:26Z","updated_at":"2018-10-24T06:44:26Z","html_url":"https://github.com/adessoAG/devblog/pull/69#discussion_r227659224","pull_request_url":"https://api.github.com/repos/adessoAG/devblog/pulls/69","author_association":"NONE","_links":{"self":{"href":"https://api.github.com/repos/adessoAG/devblog/pulls/comments/227659224"},"html":{"href":"https://github.com/adessoAG/devblog/pull/69#discussion_r227659224"},"pull_request":{"href":"https://api.github.com/repos/adessoAG/devblog/pulls/69"}},"in_reply_to_id":227420124},"pull_request":{"url":"https://api.github.com/repos/adessoAG/devblog/pulls/69","id":224670295,"node_id":"MDExOlB1bGxSZXF1ZXN0MjI0NjcwMjk1","html_url":"https://github.com/adessoAG/devblog/pull/69","diff_url":"https://github.com/adessoAG/devblog/pull/69.diff","patch_url":"https://github.com/adessoAG/devblog/pull/69.patch","issue_url":"https://api.github.com/repos/adessoAG/devblog/issues/69","number":69,"state":"open","locked":false,"title":"Apache Kafka als Streaming Plattform","user":{"login":"swindisch","id":11214403,"node_id":"MDQ6VXNlcjExMjE0NDAz","avatar_url":"https://avatars0.githubusercontent.com/u/11214403?v=4","gravatar_id":"","url":"https://api.github.com/users/swindisch","html_url":"https://github.com/swindisch","followers_url":"https://api.github.com/users/swindisch/followers","following_url":"https://api.github.com/users/swindisch/following{/other_user}","gists_url":"https://api.github.com/users/swindisch/gists{/gist_id}","starred_url":"https://api.github.com/users/swindisch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/swindisch/subscriptions","organizations_url":"https://api.github.com/users/swindisch/orgs","repos_url":"https://api.github.com/users/swindisch/repos","events_url":"https://api.github.com/users/swindisch/events{/privacy}","received_events_url":"https://api.github.com/users/swindisch/received_events","type":"User","site_admin":false},"body":"Wenn du einen Pull-Request stellst, teile uns bitte deine Änderungen mit, indem du die unten angegebene Publish-Checkliste nutzt. \r\n\r\n# Publish Checkliste\r\n\r\n- **Meta** (abzuhaken durch den Autor)\r\n  - [x] [Artikeldatei am richtigen Ort abgelegt](https://github.com/adessoAG/devblog/blob/master/examples/2017-08-10-blog-post-guide.md#dateiname-und-ablageort)\r\n  - [x] [Autoreninfo gepflegt](https://github.com/adessoAG/devblog/blob/master/examples/2017-08-10-blog-post-guide.md#autoren-informationen)\r\n  - [Metadaten gepflegt:](https://github.com/adessoAG/devblog/blob/master/examples/2017-08-10-blog-post-guide.md#metadaten)\r\n    - [x] \"title\" vergeben\r\n    - [x] \"author\" angegeben\r\n    - [x] \"categories\" enthält maximal eine der Hauptkategorien (Java, Microsoft, Methodik, Architekturen, Softwareentwicklung, Business & People)\r\n    - [x] \"tags\" enthält (beliebige) passende Tags\r\n- **Text** (abzuhaken durch den Autor)\r\n  - [x] [Teaser entspricht den Vorgaben](https://github.com/adessoAG/devblog/blob/master/examples/2017-08-10-blog-post-guide.md#einleitung--teaser)\r\n  - [x] Rechtschreibung korrigiert\r\n  - [x] [Bilder sind korrekt in den Artikel eingebunden](https://github.com/adessoAG/devblog/blob/master/examples/2017-08-10-blog-post-guide.md#bilder)\r\n  - [x] [Überschriften entsprechen den Vorgaben](https://github.com/adessoAG/devblog/blob/master/examples/2017-08-10-blog-post-guide.md#%C3%9Cberschriften)\r\n  - [x] Code-Snippets wurden mit [Syntax Highlighting](https://github.com/adessoAG/devblog/blob/master/examples/2017-08-10-blog-post-guide.md#syntax-highlighting) versehen\r\n  - [x] Der Artikel wurde in der [Vorschau](https://github.com/adessoAG/devblog/blob/master/examples/2017-08-10-blog-post-guide.md#online-preview) geprüft \r\n- **Review** (abzuhaken durch den Reviewer)\r\n  - [ ] Rechtschreibung korrigiert\r\n  - [ ] Der Artikel wurde in der [Vorschau](https://github.com/adessoAG/devblog/blob/master/examples/2017-08-10-blog-post-guide.md#online-preview) geprüft \r\n  - [ ] CCO wurde über neuen Artikel informiert\r\n  - Date-Angaben \r\n    - [ ] stimmen im Header und im Dateinamen überein\r\n    - [ ] stehen auf dem heutigen Tag (aber in der Vergangenheit)\r\n\r\n\r\n","created_at":"2018-10-22T12:50:03Z","updated_at":"2018-10-24T06:44:26Z","closed_at":null,"merged_at":null,"merge_commit_sha":"26a657f94127ac7e413cd2f881d032c61e30f97f","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/adessoAG/devblog/pulls/69/commits","review_comments_url":"https://api.github.com/repos/adessoAG/devblog/pulls/69/comments","review_comment_url":"https://api.github.com/repos/adessoAG/devblog/pulls/comments{/number}","comments_url":"https://api.github.com/repos/adessoAG/devblog/issues/69/comments","statuses_url":"https://api.github.com/repos/adessoAG/devblog/statuses/35b5146a6843f90078fbcee238b901d7ed858c34","head":{"label":"swindisch:master","ref":"master","sha":"35b5146a6843f90078fbcee238b901d7ed858c34","user":{"login":"swindisch","id":11214403,"node_id":"MDQ6VXNlcjExMjE0NDAz","avatar_url":"https://avatars0.githubusercontent.com/u/11214403?v=4","gravatar_id":"","url":"https://api.github.com/users/swindisch","html_url":"https://github.com/swindisch","followers_url":"https://api.github.com/users/swindisch/followers","following_url":"https://api.github.com/users/swindisch/following{/other_user}","gists_url":"https://api.github.com/users/swindisch/gists{/gist_id}","starred_url":"https://api.github.com/users/swindisch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/swindisch/subscriptions","organizations_url":"https://api.github.com/users/swindisch/orgs","repos_url":"https://api.github.com/users/swindisch/repos","events_url":"https://api.github.com/users/swindisch/events{/privacy}","received_events_url":"https://api.github.com/users/swindisch/received_events","type":"User","site_admin":false},"repo":{"id":154138180,"node_id":"MDEwOlJlcG9zaXRvcnkxNTQxMzgxODA=","name":"devblog","full_name":"swindisch/devblog","private":false,"owner":{"login":"swindisch","id":11214403,"node_id":"MDQ6VXNlcjExMjE0NDAz","avatar_url":"https://avatars0.githubusercontent.com/u/11214403?v=4","gravatar_id":"","url":"https://api.github.com/users/swindisch","html_url":"https://github.com/swindisch","followers_url":"https://api.github.com/users/swindisch/followers","following_url":"https://api.github.com/users/swindisch/following{/other_user}","gists_url":"https://api.github.com/users/swindisch/gists{/gist_id}","starred_url":"https://api.github.com/users/swindisch/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/swindisch/subscriptions","organizations_url":"https://api.github.com/users/swindisch/orgs","repos_url":"https://api.github.com/users/swindisch/repos","events_url":"https://api.github.com/users/swindisch/events{/privacy}","received_events_url":"https://api.github.com/users/swindisch/received_events","type":"User","site_admin":false},"html_url":"https://github.com/swindisch/devblog","description":"adesso Developer Blog","fork":true,"url":"https://api.github.com/repos/swindisch/devblog","forks_url":"https://api.github.com/repos/swindisch/devblog/forks","keys_url":"https://api.github.com/repos/swindisch/devblog/keys{/key_id}","collaborators_url":"https://api.github.com/repos/swindisch/devblog/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/swindisch/devblog/teams","hooks_url":"https://api.github.com/repos/swindisch/devblog/hooks","issue_events_url":"https://api.github.com/repos/swindisch/devblog/issues/events{/number}","events_url":"https://api.github.com/repos/swindisch/devblog/events","assignees_url":"https://api.github.com/repos/swindisch/devblog/assignees{/user}","branches_url":"https://api.github.com/repos/swindisch/devblog/branches{/branch}","tags_url":"https://api.github.com/repos/swindisch/devblog/tags","blobs_url":"https://api.github.com/repos/swindisch/devblog/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/swindisch/devblog/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/swindisch/devblog/git/refs{/sha}","trees_url":"https://api.github.com/repos/swindisch/devblog/git/trees{/sha}","statuses_url":"https://api.github.com/repos/swindisch/devblog/statuses/{sha}","languages_url":"https://api.github.com/repos/swindisch/devblog/languages","stargazers_url":"https://api.github.com/repos/swindisch/devblog/stargazers","contributors_url":"https://api.github.com/repos/swindisch/devblog/contributors","subscribers_url":"https://api.github.com/repos/swindisch/devblog/subscribers","subscription_url":"https://api.github.com/repos/swindisch/devblog/subscription","commits_url":"https://api.github.com/repos/swindisch/devblog/commits{/sha}","git_commits_url":"https://api.github.com/repos/swindisch/devblog/git/commits{/sha}","comments_url":"https://api.github.com/repos/swindisch/devblog/comments{/number}","issue_comment_url":"https://api.github.com/repos/swindisch/devblog/issues/comments{/number}","contents_url":"https://api.github.com/repos/swindisch/devblog/contents/{+path}","compare_url":"https://api.github.com/repos/swindisch/devblog/compare/{base}...{head}","merges_url":"https://api.github.com/repos/swindisch/devblog/merges","archive_url":"https://api.github.com/repos/swindisch/devblog/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/swindisch/devblog/downloads","issues_url":"https://api.github.com/repos/swindisch/devblog/issues{/number}","pulls_url":"https://api.github.com/repos/swindisch/devblog/pulls{/number}","milestones_url":"https://api.github.com/repos/swindisch/devblog/milestones{/number}","notifications_url":"https://api.github.com/repos/swindisch/devblog/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/swindisch/devblog/labels{/name}","releases_url":"https://api.github.com/repos/swindisch/devblog/releases{/id}","deployments_url":"https://api.github.com/repos/swindisch/devblog/deployments","created_at":"2018-10-22T12:16:55Z","updated_at":"2018-10-24T06:44:22Z","pushed_at":"2018-10-24T06:44:20Z","git_url":"git://github.com/swindisch/devblog.git","ssh_url":"git@github.com:swindisch/devblog.git","clone_url":"https://github.com/swindisch/devblog.git","svn_url":"https://github.com/swindisch/devblog","homepage":"https://www.adesso.de/de/news/blog/index.jsp","size":14273,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"adessoAG:master","ref":"master","sha":"25ba0a705312c31f04de5b3f8d5ce8b60de1b83c","user":{"login":"adessoAG","id":1710422,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE3MTA0MjI=","avatar_url":"https://avatars1.githubusercontent.com/u/1710422?v=4","gravatar_id":"","url":"https://api.github.com/users/adessoAG","html_url":"https://github.com/adessoAG","followers_url":"https://api.github.com/users/adessoAG/followers","following_url":"https://api.github.com/users/adessoAG/following{/other_user}","gists_url":"https://api.github.com/users/adessoAG/gists{/gist_id}","starred_url":"https://api.github.com/users/adessoAG/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/adessoAG/subscriptions","organizations_url":"https://api.github.com/users/adessoAG/orgs","repos_url":"https://api.github.com/users/adessoAG/repos","events_url":"https://api.github.com/users/adessoAG/events{/privacy}","received_events_url":"https://api.github.com/users/adessoAG/received_events","type":"Organization","site_admin":false},"repo":{"id":97385250,"node_id":"MDEwOlJlcG9zaXRvcnk5NzM4NTI1MA==","name":"devblog","full_name":"adessoAG/devblog","private":false,"owner":{"login":"adessoAG","id":1710422,"node_id":"MDEyOk9yZ2FuaXphdGlvbjE3MTA0MjI=","avatar_url":"https://avatars1.githubusercontent.com/u/1710422?v=4","gravatar_id":"","url":"https://api.github.com/users/adessoAG","html_url":"https://github.com/adessoAG","followers_url":"https://api.github.com/users/adessoAG/followers","following_url":"https://api.github.com/users/adessoAG/following{/other_user}","gists_url":"https://api.github.com/users/adessoAG/gists{/gist_id}","starred_url":"https://api.github.com/users/adessoAG/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/adessoAG/subscriptions","organizations_url":"https://api.github.com/users/adessoAG/orgs","repos_url":"https://api.github.com/users/adessoAG/repos","events_url":"https://api.github.com/users/adessoAG/events{/privacy}","received_events_url":"https://api.github.com/users/adessoAG/received_events","type":"Organization","site_admin":false},"html_url":"https://github.com/adessoAG/devblog","description":"adesso Developer Blog","fork":false,"url":"https://api.github.com/repos/adessoAG/devblog","forks_url":"https://api.github.com/repos/adessoAG/devblog/forks","keys_url":"https://api.github.com/repos/adessoAG/devblog/keys{/key_id}","collaborators_url":"https://api.github.com/repos/adessoAG/devblog/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/adessoAG/devblog/teams","hooks_url":"https://api.github.com/repos/adessoAG/devblog/hooks","issue_events_url":"https://api.github.com/repos/adessoAG/devblog/issues/events{/number}","events_url":"https://api.github.com/repos/adessoAG/devblog/events","assignees_url":"https://api.github.com/repos/adessoAG/devblog/assignees{/user}","branches_url":"https://api.github.com/repos/adessoAG/devblog/branches{/branch}","tags_url":"https://api.github.com/repos/adessoAG/devblog/tags","blobs_url":"https://api.github.com/repos/adessoAG/devblog/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/adessoAG/devblog/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/adessoAG/devblog/git/refs{/sha}","trees_url":"https://api.github.com/repos/adessoAG/devblog/git/trees{/sha}","statuses_url":"https://api.github.com/repos/adessoAG/devblog/statuses/{sha}","languages_url":"https://api.github.com/repos/adessoAG/devblog/languages","stargazers_url":"https://api.github.com/repos/adessoAG/devblog/stargazers","contributors_url":"https://api.github.com/repos/adessoAG/devblog/contributors","subscribers_url":"https://api.github.com/repos/adessoAG/devblog/subscribers","subscription_url":"https://api.github.com/repos/adessoAG/devblog/subscription","commits_url":"https://api.github.com/repos/adessoAG/devblog/commits{/sha}","git_commits_url":"https://api.github.com/repos/adessoAG/devblog/git/commits{/sha}","comments_url":"https://api.github.com/repos/adessoAG/devblog/comments{/number}","issue_comment_url":"https://api.github.com/repos/adessoAG/devblog/issues/comments{/number}","contents_url":"https://api.github.com/repos/adessoAG/devblog/contents/{+path}","compare_url":"https://api.github.com/repos/adessoAG/devblog/compare/{base}...{head}","merges_url":"https://api.github.com/repos/adessoAG/devblog/merges","archive_url":"https://api.github.com/repos/adessoAG/devblog/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/adessoAG/devblog/downloads","issues_url":"https://api.github.com/repos/adessoAG/devblog/issues{/number}","pulls_url":"https://api.github.com/repos/adessoAG/devblog/pulls{/number}","milestones_url":"https://api.github.com/repos/adessoAG/devblog/milestones{/number}","notifications_url":"https://api.github.com/repos/adessoAG/devblog/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/adessoAG/devblog/labels{/name}","releases_url":"https://api.github.com/repos/adessoAG/devblog/releases{/id}","deployments_url":"https://api.github.com/repos/adessoAG/devblog/deployments","created_at":"2017-07-16T13:31:04Z","updated_at":"2018-10-19T14:41:47Z","pushed_at":"2018-10-24T06:44:21Z","git_url":"git://github.com/adessoAG/devblog.git","ssh_url":"git@github.com:adessoAG/devblog.git","clone_url":"https://github.com/adessoAG/devblog.git","svn_url":"https://github.com/adessoAG/devblog","homepage":"https://www.adesso.de/de/news/blog/index.jsp","size":13698,"stargazers_count":9,"watchers_count":9,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":31,"mirror_url":null,"archived":false,"open_issues_count":9,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":31,"open_issues":9,"watchers":9,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/adessoAG/devblog/pulls/69"},"html":{"href":"https://github.com/adessoAG/devblog/pull/69"},"issue":{"href":"https://api.github.com/repos/adessoAG/devblog/issues/69"},"comments":{"href":"https://api.github.com/repos/adessoAG/devblog/issues/69/comments"},"review_comments":{"href":"https://api.github.com/repos/adessoAG/devblog/pulls/69/comments"},"review_comment":{"href":"https://api.github.com/repos/adessoAG/devblog/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/adessoAG/devblog/pulls/69/commits"},"statuses":{"href":"https://api.github.com/repos/adessoAG/devblog/statuses/35b5146a6843f90078fbcee238b901d7ed858c34"}},"author_association":"NONE"}}
{"actor":{"display_login":"swindisch"}}