{"action":"created","comment":{"url":"https://api.github.com/repos/adessoAG/devblog/pulls/comments/189027033","pull_request_review_id":121099424,"id":189027033,"diff_hunk":"@@ -0,0 +1,192 @@\n+---\n+layout:         [post, post-xml]              \n+title:          \"Wie sich Business-Strategien in Software umsetzen lassen\"\n+date:           2018-05-16 12:01\n+modified_date: \n+author:         ceverke \n+categories:     [Java]\n+tags:           [BRMS, Business Rules, Geschäftsregeln, Drools]\n+---\n+\n+Als einer der führenden IT-Dienstleister Deutschlands verantwortet die adesso AG IT-Projekte von Kunden verschiedener Branchen. Dabei ist zum einen fundiertes Branchenwissen von Bedeutung, zum anderen aber auch Wissen über Softwaretechnologien, um die fachlichen Kundenanforderungen softwareseitig umzusetzen. Der heutige Blogbeitrag widmet sich der Fragestellung, wie man Business-Strategien, die in Form von Geschäftsregeln definiert werden, technologisch realisieren kann. Dabei steht insbesondere die Leichtigkeit und Wartbarkeit in Vordergrund.\n+\n+# Szenario/Ausgangssituation\n+Als fortlaufendes Beispiel wird ein fachliches Szenario aus dem Bankensektor betrachtet. Dazu sei angenommen, dass eine fiktive Bank die Umsetzung einer Kundenverwaltungssoftware anstrebt. Eine der fachlichen Anforderungen hier ist die Festlegung von Kontoführungsgebühren. Eröffnet ein Kunde ein Konto bei der Bank, so müssen in Abhängigkeit von Kundendaten und Kundenwünschen die monatlichen Gebühren, wie auch das Limit für den Dispositionskredit und den zugehörigen Zinsen festgelegt werden. Die Strategie, wie sich diese Werte zusammensetzen hängt von der strategischen Ausrichtung der Bank ab. Dies wird durch das Management der Bank, unter Berücksichtigung der wirtschaftlichen Lage des Finanzmarktes festgelegt. Durch die Schwankungen des Markes ist von ständigen Änderungen auszugehen, was auch eine Änderung der Software zur Folge hat. Als Beispiel wird von folgender Strategie ausgegangen:\n+\n+Der Basispreis für ein Girokonto liegt bei monatlich drei Euro. Hier wird angenommen, dass der Kunde seine Transaktionen per Online-Banking durchführt. Da jedoch viele, insbesondere ältere Leute, vom Online-Banking keinen Gebrauch machen, bietet die Bank an, dass auch herkömmliche Transaktionen mit Überweisungsträgern möglich sind. Durch den erhöhten Verwaltungsaufwand erhöht sich allerdings die Basisgebühr um monatlich fünf Euro. Für Gemeinschaftskonten, z.B. für Eheleute oder Partner, wird häufig eine zweite Bankkarte benötigt, die die Gebühr ebenfalls um 4,50 Euro erhöht. Auf Kundenwunsch kann auch ein Premiumkonto eingerichtet werden. Hier erhält der Kunde eine Gold-Card, die mit weiteren (für dieses Beispiel nicht relevanten) Vorteilen verbunden ist. Die Zusatzkosten für diese Gold-Card betragen monatlich 9,90 Euro. \n+\n+Das Dispolimit und die zugehörigen Zinsen hängen vom Berufsstand und vom monatlichen Einkommen ab. Handelt es sich bei dem Kunden um einen Arbeitnehmer, so beträgt das Dispolimit 10% vom monatlichen Einkommen bei 10% Zinsen. Bei Rentner hingegen werden nur 5% des monatlichen Einkommens als Dispokredit zugelassen, die Zinsen verringern dafür auf 6%. Da bei Studenten von einem niedrigen Einkommen auszugehen ist, wird dieser Personengruppe keine Dispomöglichkeit eingeräumt. \n+\n+Junge Leute und Studenten werden bei der Bank ebenfalls besonders berücksichtigt. Für Studenten oder junge Leute (unter 25 Jahre) halbiert sich die auf Basis der bisher genannten Strategie festgesetzte Gebühr. Da demnach Studenten unter 25 Jahren bei doppelter Anwendung ein Rabatt von 75% erhalten würden, gilt hier, dass Studenten unter 25 Jahre 40% der ursprünglichen Gebühr zahlen müssen.\n+\n+\n+# Implementierung der Strategie\n+Nun gilt es, diese Strategie softwaretechnisch zu realisieren. Dazu werden die benötigten Eingabeparameter benötigt. Abgesehen von trivialen Informationen wie persönlichen Daten des Kunden sind hier insbesondere das monatliche Einkommen und die individuellen Bedürfnisse des Kunden (zweite Karte, Premiumkunde mit Gold-Card, Papierüberweisungen) von Bedeutung. Das zugehörige, zu implementierende Datenmodell sieht für das vereinfachte Beispiel wie folgt aus: \n+\n+![Vereinfachtes Datenmodell](/assets/images/posts/drools-post-ceverke/uml_banking_demo.png)\n+\n+Bereits aus der textuellen Beschreibung der Strategie können Konditionalsätze formalisiert werden. Diese haben allgemein die Struktur WENN [Bedingung] DANN [Konklusion], z.B. WENN *Kunde ist Student und unter 25 Jahre alt* DANN *setze Kontoführungsgebühr auf 40% des bereits ermittelten Basiswertes*. Als Softwareingenieure fällt es uns nun natürlich leicht, diese Konditionalsätze in eine Programmiersprache, z.B. Java, zu gießen. Es entstehen bedingte Anweisungen (if-else), die die Kontoführungsgebühren ermitteln. Dies wäre eine plausible und herkömmliche Umsetzungsart. \n+\n+Schnell wird man sich aber bewusst, dass diese Form der Implementierung nachteilig ist. Es ist der Umstand gegeben, dass die Implementierung  „irgendwo“ im Programmcode in Form von imperativen Anweisungen vorhanden ist. Bei großen Projekten kann es schnell schwierig werden, diese Stelle zu finden, falls Änderungsbedarf besteht. Wie eingangs genannt kann es schnell erforderlich sein, die Regeln aufgrund von Anpassungen an den Finanzmarkt zu ändern. Eine Änderung erfordert ebenfalls, dass die Software neu ausgeliefert werden muss, sollte es zu derartigen, fachlichen Änderungen kommen. Wünschenswert ist somit eine Trennung der zwischen der Implementierung der Strategie und des restlichen Programmcodes\n+\n+## Einsatz eines BRMS\n+An dieser Stelle ist ein Business Rules Management System (BRMS) nützlich, dessen Kern eine sog. Ruleengine ist. Diese Ruleengine ermöglicht es, zentral definierte Geschäftsregeln (Business-Rules) in Form von Konditionalsätzen deklarativ zu definieren und im Kontext des zugrunde liegenden Systems und Datenmodells zu prüfen und anzuwenden. Die durch die Regeln getroffenen Entscheidungen sind somit einfach zu verstehen und vor allem nachvollziehbar. \n+\n+Als konkretes Beispiel in diesem Beitrag soll die Umsetzung der genannten Strategie mit Hilfe des Businessrules-Managementsystem (BRMS) Drools gezeigt werden. Bei Drools handelt es sich um die Community-Version der kommerziellen Lösung „Red Hat Decision Managers“, der bis Anfang 2018 noch unter dem Namen „Red Hat JBoss BRMS“ geführt wurde. \n+\n+Wird in einem Projekt ein Buildmanagement Tool, wie z.B. Apache Maven oder Gradle, eingesetzt, so lässt sich Drools durch Hinzufügen der benötigten Abhängigkeiten schnell in ein Java-Projekt integrieren. Die Regeln selbst werden in einer DRL-Datei im Ressource-Verzeichnis des Projektes gespeichert, dazu später mehr. Eine kmodule.xml definiert die Struktur der Regeln. Schließlich ist davon auszugehen, dass mehrere Geschäftsregeln innerhalb einer Softwareanwendung zum Tragen kommen. Strukturiert werden die Regeln in Module, Basen und Sessions. Sessions stellen die kleinste Einheit der Strukturierung dar und können je nach Bedarf aus der Anwendung erzeugt werden. In einer Basis (kbase) wird das Paket definiert, in dem die Regeln enthalten sind. Für die Beispielanwendung zur Festlegung von Kontoführungsgebühren, deren Sourcecode übrigens auf [GitHub](https://github.com/ceverke/DroolsBrmsDemo) zur freien Verfügung steht, sieht die Definition der kmodule.xml wie folgt aus:\n+\n+```xml\n+<kmodule xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n+\txmlns=\"http://jboss.org/kie/6.0.0/kmodule\">\n+\n+\t<kbase name=\"bankAccountRules\" eventProcessingMode=\"stream\"\n+\t\tpackages=\"de.adesso.blog.businessrules\">\n+\t\t<ksession name=\"accountCostDefinitionSession\" default=\"true\" type=\"stateless\"/>\n+\t</kbase>\n+</kmodule>\n+```\n+\n+Die Basis (kbase) mit dem Namen bankAccountRules beinhaltet also die Regeln, die im Paket de.adesso.blog.businessrules enthalten sind. In dieser Basis ist eine Session enthalten (eine Default-Session vom Typ „stateless“). Eine Stateless-Session ist immer dann hilfreich, wenn durch die auszuführenden Regeln einmalig etwas berechnet, validiert oder gefiltert wird und das Ergebnis zurückgegeben wird. Eine Statefull-Session hingegen behält nach dem Treffen von Entscheidungen über eine bestimmte Zeit gespeichert und lässt die Ergebnisse in Neuberechnungen miteinfließen. Dieser Session-Typ ist insbesondere beim Complex Event Processing hilfreich, das noch in einem weiteren Blogbeitrag behandelt wird. Die kmodule.xml lässt sich sehr umfangreich und individuell für jedes Projekt konfigurieren. Für Details sei der interessierte Leser auf die Dokumentation unter drools.org verwiesen.\n+\n+\n+## Definition der Regeln in Drools\n+Der wohl spannende Teil ist wohlmöglich die Definition der Geschäftsregeln. Dies geschieht in Drools mit der sog Drools Rules Language. Die Strategie der Festsetzung der Kontoführungsgebühren ließe sich in der DRL wie folgt implementieren:\n+package de.adesso.blog.businessrules;\n+\n+```java\n+import de.adesso.blog.model.*;\n+\n+\n+rule \"1. Default account maintenance charge: 3.0 Euro per month\"\n+\twhen\n+\t\t$customer : PrivateCustomer(bankAccount==null) \n+\tthen\n+\t\tBankAccount $bankAccount = new BankAccount();\n+\t\t$bankAccount.setCostPerMonth(3.0);\n+\t\t$customer.setBankAccount($bankAccount);\n+\t\tinsert($customer);\n+end\n+\n+rule \"1.a Customer needs non online transferals; additional charge 5.0 Euro per month\"\n+\twhen\n+\t\t$customer : PrivateCustomer(nonOnlineTransferals) \n+\tthen\n+\t\tBankAccount $bankAccount = $customer.getBankAccount();\n+\t\t$bankAccount.setCostPerMonth($bankAccount.getCostPerMonth() + 5.0);\n+\t\t$customer.setBankAccount($bankAccount);\n+\t\tinsert($customer);\n+end\n+\n+rule \"1.b Customer needs second card; additional charge 4.50 Euro per month\"\n+\twhen\n+\t\t$customer : PrivateCustomer(secondCard) \n+\tthen\n+\t\tBankAccount $bankAccount = $customer.getBankAccount();\n+\t\t$bankAccount.setCostPerMonth($bankAccount.getCostPerMonth() + 4.5);\n+\t\t$customer.setBankAccount($bankAccount);\n+\t\tinsert($customer);\n+end\n+\n+\n+\n+rule \"1.c Customer needs gold card; additional charge 9.90 Euro per month\"\n+\twhen\n+\t\t$customer : PrivateCustomer(goldCard) \n+\tthen\n+\t\tBankAccount $bankAccount = $customer.getBankAccount();\n+\t\t$bankAccount.setCostPerMonth($bankAccount.getCostPerMonth() + 9.9);\n+\t\t$customer.setBankAccount($bankAccount);\n+\t\tinsert($customer);\t\t\n+end\n+\n+rule \"2.a Customer is employee, overdraft limit is 10% of the monthly salary (with an interest of 10.9%)\"\n+\twhen\n+\t\t$customer : PrivateCustomer(jobState==JobState.EMPLOYEE) \n+\tthen\n+\t\tBankAccount $bankAccount = $customer.getBankAccount();\n+\t\t$bankAccount.setOverdraft($customer.getMonthlySalaray() * 0.1);\n+\t\t$bankAccount.setOverdraftInterest(10.9);\n+\t\t$customer.setBankAccount($bankAccount);\n+\t\tinsert($customer);\n+end\n+\n+rule \"2.b Customer is student, there is no overdraft limit (and, of course, no interest)\"\n+\twhen\n+\t\t$customer : PrivateCustomer(jobState==JobState.STUDENT) \n+\tthen\n+\t\tBankAccount $bankAccount = $customer.getBankAccount();\n+\t\t$bankAccount.setOverdraft(0.0);\n+\t\t$bankAccount.setOverdraftInterest(0.0);\n+\t\t$customer.setBankAccount($bankAccount);\n+\t\tinsert($customer);\n+end\n+\n+rule \"2.a Customer is pensioner, overdraft limit is 5% of the monthly salary (with an interest of 6%)\"\n+\twhen\n+\t\t$customer : PrivateCustomer(jobState==JobState.PENSIONER) \n+\tthen\n+\t\tBankAccount $bankAccount = $customer.getBankAccount();\n+\t\t$bankAccount.setOverdraft($customer.getMonthlySalaray() * 0.05);\n+\t\t$bankAccount.setOverdraftInterest(6.0);\n+\t\t$customer.setBankAccount($bankAccount);\n+\t\tinsert($customer);\n+end\n+\n+rule \"3.a + 3b  Students or persons under 25 yrs (but not both) only pay half of the calculated fee\"\n+\twhen\n+\t\t$customer : PrivateCustomer((jobState==JobState.STUDENT || calculateAge()<25) && !(\n+\t\tjobState==JobState.STUDENT && calculateAge()<25)) \n+\tthen\n+\t\tBankAccount $bankAccount = $customer.getBankAccount();\n+\t\t$bankAccount.setCostPerMonth($bankAccount.getCostPerMonth()/2);\n+\t\t$customer.setBankAccount($bankAccount);\n+\t\tinsert($customer);\n+end\n+\n+rule \"3.c Persons under 25 yrs. that are students pay 40% of the calculated fee\"\n+\twhen\n+\t\t$customer : PrivateCustomer(calculateAge()<25 && jobState==JobState.STUDENT) \n+\tthen\n+\t\tBankAccount $bankAccount = $customer.getBankAccount();\n+\t\t$bankAccount.setCostPerMonth($bankAccount.getCostPerMonth()*0.4);\n+\t\t$customer.setBankAccount($bankAccount);\n+\t\tinsert($customer);\n+end\n+```\n+\n+Die definierten Regeln sind dahingehend vollständig, dass sie die gesamte Strategie des fiktiven Beispiels umsetzen. Zeile 1 definiert das Package, in dem die Regeln enthalten sind. Der import-Befehl in Zeile 3 importiert das Datenmodell, das bereits in obiger Abbildung dargestellt ist. Dieser Import ist erforderlich, da schließlich die fehlenden Werte für das zum Kunden in Relation stehende Konto ermittelt werden sollen. Die Annahme ist, dass es sich um einen Neukunden handelt und somit beim Aufruf der Regeln das zugehörige Konto auf null gesetzt ist. Auf dieser Annahme wird auch die erste Regel aktiv. Diese wird ausgeführt (dies wird auch als „feuern“ einer Regel bezeichnet), wenn in die Session ein Objekt vom Typ PrivateCustomer eingeführt wird, dessen Wert für das zugehörige Konto auf null gesetzt ist. \n+\n+Die Bedingungen, die für das eingefügte Objekt überprüft werden, werden in den runden Klammern angegeben, daher auch der Ausdruck  $customer : PrivateCustomer(bankAccount==null). Alle verwendeten Variablen, die nicht mit einem $ beginnen, beziehen sich auf das entsprechende Objekt oder auf ein zugehöriges Attribut (wie z.B. bankAccount). Voraussetzung ist also, dass das Attribut bankAccount im Objekt PrivateCustomer vorhanden ist – andernfalls kommt es beim Build-Prozess der Applikation zu einem Kompilierfehler. Variablen mit dem Prefix $ hingegen werden innerhalb der Regeln verwendet. Wird also eine Instanz vom Typ PrivateCustomer mit bankAccount==null eingefügt, so referenziert $customer dieses Instanz und kann diese im then-Teil modifizieren. Der then-Teil erstellt nun ein neues Objekt vom Typ BankAccount, referenziert dieses über die Variable $bankAccount, setzt die Kontoführungsgebühr gemäß der Strategie auf drei Euro und setzt das neue Konto in Relation zu dem PrivateCustomer. Damit auch alle weiteren Regeln Anwendung finden, muss das modifizierte Objekt wieder neu in die Session eingefügt werden. Dies macht in der letzte Befehl im then-Teil der Regel.\n+\n+Das Prinzip wird so immer fortgesetzt. Da nun das Attribut bankAccount der gerade modifizierten Instanz der Klasse PrivateCustomer nicht mehr den Wert null besitzt und die Instanz neu in die Session eingeführt wurde, werden nach und nach die Prämissen der anderen Regeln erfüllt und somit die weiteren Werte nach und nach determiniert. \n+\n+Bei der Definition ist natürlich eine präzise Formulierung von besonderer Bedeutung. Wie der genannten Strategie zu entnehmen, bekommen Studenten und Personen unter 25 Jahre eine Ermäßigung von 50% auf die Gebühren. Eine unachtsame Implementierung könnte jedoch dazu führen, wenn Regel 3a+3b das logische ODER statt dem exklusiven ODER implementiert wird.  In einem solchen Fall bekämen Studenten, die unter 25 Jahre alt sind, zunächst hälftig einen Rabatt und in Regel 3c erneut 60 Prozent Ermäßigung, was aber nicht der definierten Strategie entspricht. ","path":"_posts/2018-05-02-brms-basisc-with-drools.md","position":164,"original_position":164,"commit_id":"1d63b71b270820ff17e579ff6db870d7156fc55f","original_commit_id":"1d63b71b270820ff17e579ff6db870d7156fc55f","user":{"login":"silasmahler","id":14834495,"avatar_url":"https://avatars1.githubusercontent.com/u/14834495?v=4","gravatar_id":"","url":"https://api.github.com/users/silasmahler","html_url":"https://github.com/silasmahler","followers_url":"https://api.github.com/users/silasmahler/followers","following_url":"https://api.github.com/users/silasmahler/following{/other_user}","gists_url":"https://api.github.com/users/silasmahler/gists{/gist_id}","starred_url":"https://api.github.com/users/silasmahler/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/silasmahler/subscriptions","organizations_url":"https://api.github.com/users/silasmahler/orgs","repos_url":"https://api.github.com/users/silasmahler/repos","events_url":"https://api.github.com/users/silasmahler/events{/privacy}","received_events_url":"https://api.github.com/users/silasmahler/received_events","type":"User","site_admin":false},"body":"unter 25 Jahren\r\n\r\nSatzstruktur: Eine unachtsame Implementierung .....implementiert wird.\r\n\r\nhälftig einen Rabatt hört sich komisch an, ob das korrekt ist, weis ich nicht\r\n\r\n","created_at":"2018-05-17T16:45:31Z","updated_at":"2018-05-17T16:55:01Z","html_url":"https://github.com/adessoAG/devblog/pull/52#discussion_r189027033","pull_request_url":"https://api.github.com/repos/adessoAG/devblog/pulls/52","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/adessoAG/devblog/pulls/comments/189027033"},"html":{"href":"https://github.com/adessoAG/devblog/pull/52#discussion_r189027033"},"pull_request":{"href":"https://api.github.com/repos/adessoAG/devblog/pulls/52"}}},"pull_request":{"url":"https://api.github.com/repos/adessoAG/devblog/pulls/52","id":188509651,"html_url":"https://github.com/adessoAG/devblog/pull/52","diff_url":"https://github.com/adessoAG/devblog/pull/52.diff","patch_url":"https://github.com/adessoAG/devblog/pull/52.patch","issue_url":"https://api.github.com/repos/adessoAG/devblog/issues/52","number":52,"state":"open","locked":false,"title":"Blogbeitrag zu BRMS (Drools)","user":{"login":"ceverke","id":25300821,"avatar_url":"https://avatars2.githubusercontent.com/u/25300821?v=4","gravatar_id":"","url":"https://api.github.com/users/ceverke","html_url":"https://github.com/ceverke","followers_url":"https://api.github.com/users/ceverke/followers","following_url":"https://api.github.com/users/ceverke/following{/other_user}","gists_url":"https://api.github.com/users/ceverke/gists{/gist_id}","starred_url":"https://api.github.com/users/ceverke/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ceverke/subscriptions","organizations_url":"https://api.github.com/users/ceverke/orgs","repos_url":"https://api.github.com/users/ceverke/repos","events_url":"https://api.github.com/users/ceverke/events{/privacy}","received_events_url":"https://api.github.com/users/ceverke/received_events","type":"User","site_admin":false},"body":"PR created - reviewer needed\r\n\r\n","created_at":"2018-05-16T17:43:11Z","updated_at":"2018-05-17T16:55:01Z","closed_at":null,"merged_at":null,"merge_commit_sha":"63178e76753427a414f6287967f97363351b2c12","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/adessoAG/devblog/pulls/52/commits","review_comments_url":"https://api.github.com/repos/adessoAG/devblog/pulls/52/comments","review_comment_url":"https://api.github.com/repos/adessoAG/devblog/pulls/comments{/number}","comments_url":"https://api.github.com/repos/adessoAG/devblog/issues/52/comments","statuses_url":"https://api.github.com/repos/adessoAG/devblog/statuses/1d63b71b270820ff17e579ff6db870d7156fc55f","head":{"label":"ceverke:master","ref":"master","sha":"1d63b71b270820ff17e579ff6db870d7156fc55f","user":{"login":"ceverke","id":25300821,"avatar_url":"https://avatars2.githubusercontent.com/u/25300821?v=4","gravatar_id":"","url":"https://api.github.com/users/ceverke","html_url":"https://github.com/ceverke","followers_url":"https://api.github.com/users/ceverke/followers","following_url":"https://api.github.com/users/ceverke/following{/other_user}","gists_url":"https://api.github.com/users/ceverke/gists{/gist_id}","starred_url":"https://api.github.com/users/ceverke/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ceverke/subscriptions","organizations_url":"https://api.github.com/users/ceverke/orgs","repos_url":"https://api.github.com/users/ceverke/repos","events_url":"https://api.github.com/users/ceverke/events{/privacy}","received_events_url":"https://api.github.com/users/ceverke/received_events","type":"User","site_admin":false},"repo":{"id":131494030,"name":"devblog","full_name":"ceverke/devblog","owner":{"login":"ceverke","id":25300821,"avatar_url":"https://avatars2.githubusercontent.com/u/25300821?v=4","gravatar_id":"","url":"https://api.github.com/users/ceverke","html_url":"https://github.com/ceverke","followers_url":"https://api.github.com/users/ceverke/followers","following_url":"https://api.github.com/users/ceverke/following{/other_user}","gists_url":"https://api.github.com/users/ceverke/gists{/gist_id}","starred_url":"https://api.github.com/users/ceverke/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ceverke/subscriptions","organizations_url":"https://api.github.com/users/ceverke/orgs","repos_url":"https://api.github.com/users/ceverke/repos","events_url":"https://api.github.com/users/ceverke/events{/privacy}","received_events_url":"https://api.github.com/users/ceverke/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/ceverke/devblog","description":"adesso Developer Blog","fork":true,"url":"https://api.github.com/repos/ceverke/devblog","forks_url":"https://api.github.com/repos/ceverke/devblog/forks","keys_url":"https://api.github.com/repos/ceverke/devblog/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ceverke/devblog/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ceverke/devblog/teams","hooks_url":"https://api.github.com/repos/ceverke/devblog/hooks","issue_events_url":"https://api.github.com/repos/ceverke/devblog/issues/events{/number}","events_url":"https://api.github.com/repos/ceverke/devblog/events","assignees_url":"https://api.github.com/repos/ceverke/devblog/assignees{/user}","branches_url":"https://api.github.com/repos/ceverke/devblog/branches{/branch}","tags_url":"https://api.github.com/repos/ceverke/devblog/tags","blobs_url":"https://api.github.com/repos/ceverke/devblog/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ceverke/devblog/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ceverke/devblog/git/refs{/sha}","trees_url":"https://api.github.com/repos/ceverke/devblog/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ceverke/devblog/statuses/{sha}","languages_url":"https://api.github.com/repos/ceverke/devblog/languages","stargazers_url":"https://api.github.com/repos/ceverke/devblog/stargazers","contributors_url":"https://api.github.com/repos/ceverke/devblog/contributors","subscribers_url":"https://api.github.com/repos/ceverke/devblog/subscribers","subscription_url":"https://api.github.com/repos/ceverke/devblog/subscription","commits_url":"https://api.github.com/repos/ceverke/devblog/commits{/sha}","git_commits_url":"https://api.github.com/repos/ceverke/devblog/git/commits{/sha}","comments_url":"https://api.github.com/repos/ceverke/devblog/comments{/number}","issue_comment_url":"https://api.github.com/repos/ceverke/devblog/issues/comments{/number}","contents_url":"https://api.github.com/repos/ceverke/devblog/contents/{+path}","compare_url":"https://api.github.com/repos/ceverke/devblog/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ceverke/devblog/merges","archive_url":"https://api.github.com/repos/ceverke/devblog/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ceverke/devblog/downloads","issues_url":"https://api.github.com/repos/ceverke/devblog/issues{/number}","pulls_url":"https://api.github.com/repos/ceverke/devblog/pulls{/number}","milestones_url":"https://api.github.com/repos/ceverke/devblog/milestones{/number}","notifications_url":"https://api.github.com/repos/ceverke/devblog/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ceverke/devblog/labels{/name}","releases_url":"https://api.github.com/repos/ceverke/devblog/releases{/id}","deployments_url":"https://api.github.com/repos/ceverke/devblog/deployments","created_at":"2018-04-29T12:48:25Z","updated_at":"2018-05-16T17:36:44Z","pushed_at":"2018-05-16T17:36:43Z","git_url":"git://github.com/ceverke/devblog.git","ssh_url":"git@github.com:ceverke/devblog.git","clone_url":"https://github.com/ceverke/devblog.git","svn_url":"https://github.com/ceverke/devblog","homepage":"https://www.adesso.de/de/news/blog/index.jsp","size":5781,"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"},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"adessoAG:master","ref":"master","sha":"8512476243576bee6865fd3dd25eb9748b1f983c","user":{"login":"adessoAG","id":1710422,"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,"name":"devblog","full_name":"adessoAG/devblog","owner":{"login":"adessoAG","id":1710422,"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},"private":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-04-30T09:34:22Z","pushed_at":"2018-05-16T17:43:12Z","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":2711,"stargazers_count":4,"watchers_count":4,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":15,"mirror_url":null,"archived":false,"open_issues_count":4,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit"},"forks":15,"open_issues":4,"watchers":4,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/adessoAG/devblog/pulls/52"},"html":{"href":"https://github.com/adessoAG/devblog/pull/52"},"issue":{"href":"https://api.github.com/repos/adessoAG/devblog/issues/52"},"comments":{"href":"https://api.github.com/repos/adessoAG/devblog/issues/52/comments"},"review_comments":{"href":"https://api.github.com/repos/adessoAG/devblog/pulls/52/comments"},"review_comment":{"href":"https://api.github.com/repos/adessoAG/devblog/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/adessoAG/devblog/pulls/52/commits"},"statuses":{"href":"https://api.github.com/repos/adessoAG/devblog/statuses/1d63b71b270820ff17e579ff6db870d7156fc55f"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"silasmahler"}}