Kochbuch: Maven Codebeispiele

In unserem Git Repository befindet sich eine umfangreiche Sammlung an verschiedenen Codebeispielen für Apache Maven Projekte. Alles gut übersichtlich nach Themengebieten sortiert.

Zurück zum Inhaltsverzeichniss: Apache Maven Master Class

  1. Token Replacement
  2. Compiler Warnings
  3. Excecutable JAR Files
  4. Enforcments
  5. Unit & Integration Testing
  6. Multi Module Project (JAR / WAR)
  7. BOM – Bill Of Materials (Dependency Management)
  8. Running ANT Tasks
  9. License Header – Plugin
  10. OWASP
  11. Profiles
  12. Maven Wrapper
  13. Shade Ueber JAR (Plugin)
  14. Java API Documantation (JavaDoc)
  15. Java Sources & Test Case packaging into JARs
  16. Docker
  17. Assemblies
  18. Maven Reporting (site)
  19. Flatten a POM
  20. GPG Signer

Computer Science Library – Top 10 Programmier Bücher

Für Hobbyprogrammierer als auch professionelle Softwareentwickler sind gut Informationsquellen essenziell. Eine kleine, überschaubare Privatbibliothek mit zeitlosen Büchern über Programmierung ist daher immer eine gute Sache. Leider ist das Angebot zu IT-Literatur sehr umfangreich und oft veralten die Bücher auch schnell wieder. Hinzu kommt außerdem noch, dass einige Titel aus unterschiedlichen Gründen nicht unbedingt das Prädikat lesenswert besitzen. Manche Texte sind nur sehr verständlich. Andere wiederum enthalten kaum relevante Informationen, die bereits leicht über öffentliche Quellen bezogen werden können. Deswegen habe ich mir einmal die Mühe gemacht und meine Top 10 Bücher zum Thema Softwareentwicklung zusammengestellt.

Alle Titel sind im Original in englischer Sprache veröffentlicht worden. Die meisten davon wurden aber auch ins Deutsche übersetzt. Wem das Lesen englischer Bücher keine Schwierigkeiten bereitet, sollte sich das Original besorgen, da manchmal die Übersetzungen etwas holprig sind.

Ein wichtiges Kriterium für eine Auswahl ist, dass die Bücher sehr generell sind und sich nicht auf eine spezifische Version beschränken. Hinzu kommt noch, dass ich die hier vorgeschlagenen Werke auch tatsächlich in meinem Bücherregal stehen habe, und daher auch gelesen habe.

  1. Effective Java 3rd Edition, J. Bloch, 2017, ISBN: 0-134-68599-7 |
    Für alle Java-Entwickler, das Standardwerk mit vielen Hintergrundinformationen über die Funktionsweise der Sprache und Optimierungen des eigenen Source Codes.
  2. The Linux Command Line 2nd Edition, W. Shotts, 2019, ISBN: 1-59327-952-3 |
    Linux hat in der Softwareentwicklung einen hohen Stellenwert, da nicht nur Cloud-Anwendungen in Linux Umgebungen deployed werden. Um so wichtiger ist es sich auf der Kommandozeile sicher bewegen zu können. Dieses Buch widmet sich ausschließlich dem Umgang mit der Bash und ist für alle Linux Distributionen geeignet.
  3. Angry Tests, Y. Bugayenko, 2025, ISBN: 978-1982063740 |
    Testgetriebene Softwareentwicklung ist eine wichtige Fähigkeit, um eine hohe Qualität sicherzustellen. Dieses Buch ist nicht auf eine konkrete Programmiersprache ausgerichtet, sondern befasst sich ausschließlich damit, wie man aussagekräftige Testfälle schreibt.
  4. Clean Architecture, R. C. Martin, 2018, ISBN: 0-13-449416-4 |
    Neben einem Abriss der Historie, wie die verschiedenen Programmier-Paradigmen in Beziehung zueinander stehen, beschreibt das Buch grundlegende Architekturentwurfsstile. Nicht nur für Softwarearchitekten, sondern auch für Entwickler sehr lesenswert.
  5. Mastering Regular Expressions 3rd Edition, J. E. F. Friedl, 2006, ISBN: 0-596-52812-4 |
    Das absolute Standardwerk zum Thema reguläre Ausdrücke. Ein Muss für jeden, der das Thema wirklich verstehen muss.
  6. Head First Design Pattern, Eric & Elisabeth Freeman, 2004, ISBN: 0-596-00712-4 |
    Entwurfsmuster gehören zu den Grundfähigkeiten eines jeden Programmierers. In diesem Buch werden die einzelnen Konzepte der GOF Muster umfassend besprochen. Es eignet sich sowohl zum Einstieg als auch als Referenz.
  7. Advanced API Security 2nd Edition, P. Siriwardena, 2020, ISBN: 978-1-4842-2049-8 |
    API Entwurf für RESTful Services gehört mittlerweile zum Standardrepertoire eines Entwicklers. Aber auch das Thema Sicherheit darf dabei nicht zu kurz kommen. Dieses Buch bespricht neue Konzepte, die zum Industriestandard erhoben wurden. Ein guter Einstieg für Programmierer, die bisher nicht mit SAML, OAuth und Open ConnectID in Berührung gekommen sind.
  8. SQL Antipatterns, B. Karwin, 2010, ISBN: 987-1-934356-55-5 |
    Selbst für gestandene Programmierer sind Datenbanken oft ein Buch mit sieben Siegeln. Auch wenn SQL Statements schnell hingeschrieben sind und diese auch das gewünschte Resultat hervorbringen, können im Produktivbetrieb zu erheblichen Problemen führen. Dieses Buch beschreibt, warum Statements sehr langsam ausgeführt werden und wie diese richtig formuliert werden können.
  9. Domain Driven Design, E. Evans, 2003, ISBN: 0-32-112521-5 |
    Die Verbindung zwischen objektorientierter Programmierung (OOP) und Datenbankentwurf wird mit dem Paradigma Domain Driven Design geschlagen.
  10. The Art of Computer Programming I-IV, D.E. Knuth, 2021, ISBN: 0-137-93510-2 |
    Vier einzelne Bücher im Schuber beschreiben auf sehr mathematische Weise wie Algorithmen funktionieren.

Flaschenhals Pull Requests

Der sichere Umgang mit Source Control Management (SCM) Systemen wie Git ist für Programmierer (Development) und auch Systemadministratoren (Operations) essenziell. Diese Gruppe von Werkzeugen hat eine lange Tradition in der Softwareentwicklung und versetzt Entwicklungsteams in die Lage, gemeinsam an einer Codebasis zu arbeiten. Dabei werden vier Fragen beantwortet: Wann wurde die Änderung gemacht? Wer hat die Änderung vorgenommen? Was wurde geändert? Warum wurde etwas geändert? Es ist also ein reines Kollaborationswerkzeug.

Mit dem Aufkommen der Open Source Code Hosting Plattform GitHub wurden sogenannte Pull Requests eingeführt. Pull Requests ist in GitHub ein Workflow, der es Entwicklern erlaubt, Codeänderungen für Repositories bereitzustellen, auf die sie nur lesenden Zugriff haben. Erst nachdem der Besitzer des originalen Repositories die vorgeschlagenen Änderungen überprüft und für gut befunden hat, werden diese Änderungen von ihm übernommen. So setzt sich auch die Bezeichnung zusammen. Ein Entwickler kopiert sozusagen das originale Repository in seinen GitHub Arbeitsbereich, nimmt Änderungen vor und stellt an den Inhaber des originalen Repositories eine Anfrage, die Änderung zu übernehmen. Dieser kann dann die Änderungen übernehmen und gegebenenfalls noch selbst anpassen oder mit einer Begründung zurückweisen.

Wer nun glaubt, dass GitHub besonders innovativ war, der irrt. Denn dieser Prozess ist in der Open Source Community ein ‚sehr‘ alter Hut. Ursprünglich nennt man dieses Vorgehen Dictatorship Workflow. Das 1990 zum ersten Mal veröffentlichte kommerzielle SCM Rational Synergy von IBM basiert genau auf dem Dictarorship Workflow. Mit der Klasse der verteilten Versionsverwaltungswerkzeuge, zu denen auch Git gehört, lässt sich der Dictatorship Worflow recht einfach umsetzen. Also lag es auf der Hand das GitHub diesen Prozess seinen Nutzern auch zur Verfügung stellt. Lediglich die Namensgebung ist von GitHub weitaus ansprechender gewählt. Wer beispielsweise mit der freien DevOps Lösung GitLab arbeitet, kennt Pull Requests unter der Bezeichnung Merge Requests. Mittlerweile enthalten die gängigsten Git-Server den Prozess der Pull Requests. Ohne zu sehr auf die technischen Details zur Umsetzung der Pull Request einzugehen, richten wir unsere Aufmerksamkeit auf die üblichen Probleme mit denen Open Source Projekte konfrontiert sind.

Entwickler, die sich an einem Open Source Projekt beteiligen möchten, werden Maintainer genannt. Nahezu jedes Projekt hat eine kleine Anleitung, wie man das entsprechende Projekt unterstützen kann und welche Regeln gelten. Für Personen, die das Programmieren erlernen, eignen sich Open Source Projekte hervorragend, um die eigenen Fähigkeiten schnell signifikant zu verbessern. Das bedeutet für das Open Source Projekt, dass man Maintainer mit den unterschiedlichsten Fähigkeiten und Erfahrungsschatz hat. Wenn man also keinen Kontrollmechanismus etabliert, erodiert die Codebasis in sehr kurzer Zeit. Wenn das Projekt nun recht groß ist und sehr viele Mainatainer auf der Codebasis agieren, ist es für den Inhaber des Repositories kaum noch möglich, alle Pull Requests zeitnahe zu bearbeiten. Um diesem Bottelneck entgegenzuwirken, wurde der Dictatorship Workflow zum Dictatorship – Lieutenant Workflow erweitert. Es wurde also eine Zwischeninstanz eingeführt, mit der die Überprüfung der Pull Requests auf mehrere Schultern verteilt wird. Diese Zwischenschicht, die sogenannten Lieutenants sind besonders aktive Maintainer mit einer bereits etablierten Reputation. Somit braucht der Dictator nur noch die Pull Requests der Lieutenants zu reviewen. Eine ungemeine Arbeitsentlastung, die sicherstellt, dass es zu keinem Feature Stau durch nicht abgearbeitete Pull Requests kommt. Schließlich sollen die Verbesserungen beziehungsweise die Erweiterungen so schnell wie möglich in die Codebasis aufgenommen werden, um dann im nächsten Release den Nutzern zur Verfügung zu stehen.

Dieses Vorgehen ist bis heute der Standard in Open Source Projekten, um Qualität gewährleisten zu können. Man kann ja nie sagen, wer sich alles am Projekt beteiligt. Möglicherweise mag es ja auch den ein oder anderen Saboteur geben. Diese Überlegung ist nicht so abwegig. Unternehmen, die für ihr kommerzielles Produkt eine starke Konkurrenz aus dem feien Open Source Bereich haben, könnten hier auf unfaire Gedanken kommen, wenn es keine Reglementierungen geben würde. Außerdem lassen sich Maintainer nicht disziplinieren, wie es beispielsweise für Teammitglieder in Unternehmen gilt. Einem beratungsresistenten Maintainer, der sich trotz mehrfachen Bitten nicht an die Vorgaben des Projektes hält, kann man schwer mit Gehaltskürzungen drohen. Einzige Handhabe ist diese Person vom Projekt auszuschließen.

Auch wenn das gerade beschriebene Problem der Disziplinierung von Mitarbeitern in kommerziellen Teams kein Problem darstellt, gibt es in diesen Umgebungen ebenfalls Schwierigkeiten, die es zu meistern gilt. Diese Probleme rühren noch aus den Anfängen von Visualisierungswerkzeugen. Denn die ersten Vertreter dieser Spezies waren keine verteilten Lösungen, sondern zentralisiert. CVS und Subversion (SVN) halten auf dem lokalen Entwicklungsrechner immer nur die letzte Revision der Codebasis. Ohne Verbindung zum Server kann man faktisch nicht arbeiten. Bei Git ist dies anders. Hier hat man eine Kopie des Repositories auf dem eigenen Rechner, sodass man seine Arbeiten lokal in einem separaten Branch durchführt und wenn man fertig ist, bringt man diese Änderungen in den Hauptentwicklungszweig und überträgt diese dann auf den Server. Die Möglichkeit, offline Branches zu erstellen und diese lokal zu mergen hat einen entscheidenden Einfluss auf die Stabilität der eigenen Arbeit, wenn das Reopsitory in einen inkonsistenten Zustand gerät. Denn im Gegensatz zu zentralisierten SCM Systemen kann man nun weiter arbeiten, ohne darauf warten zu müssen, dass der Hauptentwicklungszweig repariert wurde.

Diese Inkonsistenten entstehen sehr leicht. Es genügt nur eine Datei beim Commit zu vergessen und schon können die Teamkollegen das Projekt nicht mehr lokal kompilieren und sind in der Arbeit behindert. Um diesem Problem Herr zu werden, wurde das Konzept Continuous Integration (CI) etabliert. Es handelt sich dabei nicht, wie oft fälschlicherweise angenommen, um die Integration verschiedener Komponenten zu einer Anwendung. Die Zielstellung bei CI ist die Commit Satge – das Code Repository – in einem konsistenten Zustand zu halten. Dazu wurden Build Server etabliert, die in regelmäßigen Abständen das Repository auf Änderungen überprüfen, um dann den aus dem Quelltext das Artefakt bauen. Ein sehr beliebter und seit vielen Jahren etablierter Build-Server ist Jenkins. Jenkins ging ursprünglich aus dem Projekt Hudson als Abspaltung hervor und übernahm mittlerweile viele weitere Aufgaben. Deswegen ist es sehr sinnvoll, diese Klasse von Tools als Automatisierungsserver zu bezeichnen.

Mit diesem kleinen Abriss in die Geschichte der Softwareentwicklung verstehen wir nun die Probleme von Open Source Projekten und kommerzieller Softwareentwicklung. Dazu haben wir die Entstehungsgeschichte der Pull Request besprochen. Indessen kommt es in kommerziellen Projekten sehr oft vor, dass Teams durch das Projektmanagement gezwungen werden mit Pull Requests zu arbeiten. Für einen Projektleiter ohne technisches Hintergrundwissen klingt es nun sehr sinnvoll, in seinem Projekt ebenfalls Pull Requests zu etablieren. Schließlich hat er die Idee, dass er somit die Codequalität verbessert. Leider ist das aber nicht der Fall. Das Einzige was passiert ist ein Feature Stau zu provozieren und eine erhöhte Auslastung des Teams zu erzwingen, ohne die Produktivität zu verbessern. Denn der Pull Request muss ja von einer kompetenten Person inhaltlich bewertet werden. Das verursacht bei großen Projekten unangenehme Verzögerungen.

Nun erlebe ich es oft, dass argumentiert wird, man könne die Pull Requests ja automatisieren. Das heißt, der Build Server nimmt den Branch, mit dem Pull Request versucht diesen zu bauen und im Fall dass das Kompilieren und die automatisierten Tests erfolgreich sind, versucht der Server die Änderungen in den Hauptentwicklungszweig zu übernehmen. Möglicherweise sehe ich da etwas falsch, aber wo ist die Qualitätskontrolle? Es handelt sich um einen einfachen Continuous Integration Prozess, der die Konsistenz des Repositories aufrechterhält. Da Pull Requests vornehmlich im Git Umfeld zu finden sind, bedeutet ein kurzzeitig inkonsistentes Repository kein kompletten Entwicklungstop für das gesamte Team, wie es bei Subversion der Fall ist.

Interessant ist auch die Frage wie man bei einem automatischen Merge mit semantischen Mergekonflikten umgeht. Die per se kein gravierendes Problem sind. Sicher führt das zur Ablehnung des Pull Requests mit entsprechender Nachricht an den Entwickler, um das Problem mit einem neuen Pull Request zu lösen. Ungünstige Branchstrategien können hier allerdings zu unverhältnismäßigen Mehraufwand führen.

Für die Verwendung von Pull Requests in kommerziellen Softwareprojekten sehe ich keinen Mehrwert, weswegen ich davon abrate, in diesem Kontext Pull Request zu verwenden. Außer einer Verkomplizierung der CI / CD Pipeline und einem erhöhten Ressourcenverbrauch des Automatisierungsservers, der nun die Arbeit doppelt macht, ist nichts passiert. Die Qualität eines Softwareprojektes verbessert man durch das Einführen von automatisierten Unit-Tests und einem testgetriebenen Vorgehen bei der Umsetzung von Features. Hier ist es notwendig, die Testabdeckung des Projekts kontinuierlich im Auge zu behalten und zu verbessern. Statische Codeanalyse und das Aktivieren von Compilerwarnings bringen mit erheblich weniger Aufwand bessere Ergebnisse.

Ich persönlich vertrete die Auffassung, dass Unternehmen, die auf Pull Requests setzen, diese entweder für ein verkompliziertes CI nutzen oder ihren Entwicklern komplett misstrauen und ihnen in Abrede stellen, gute Arbeit abzuliefern. Natürlich bin ich offen für eine Diskussion zum Thema, möglicherweise lässt sich dann eine noch bessere Lösung finden. Von daher freue ich mich über reichliche Kommentare mit euren Ansichten und Erfahrungen im Umgang mit Pull Requests.

Konfigurationsdateien in Softwareanwendungen

Wieso benötigen wir überhaupt die Möglichkeit, Konfigurationen einer Anwendung in Textdateien zu speichern? Genügt nicht einfach eine Datenbank für diesen Zweck? Die Antwort auf diese Frage ist recht trivial. Denn die Information, wie sich eine Anwendung mit einer Datenbank verbinden kann, lässt sich ja schlecht in der Datenbank selbst speichern.

Jetzt könnte man sicher argumentieren, dass man solche Dinge mit einer integrierten Datenbank (embedded) wie beispielsweise SQLite hinbekommt. Das mag auch grundsätzlich korrekt sein. Leider ist diese Lösung für hoch skalierbare Anwendungen nicht wirklich praktikabel. Zudem muss man nicht immer gleich mit Kanonen auf Spatzen schießen. Das Speichern wichtiger Konfigurationsparameter in Textdateien hat bereits eine lange Tradition in der Softwareentwicklung. Mittlerweile haben sich aber auch verschiedene Textformate wie INI, XML, JSON und YAML für diesen Anwendungsfall etabliert. Angesichts dessen stellt sich die Frage, auf welches Format man am besten für das eigene Projekt zurückgreifen sollte.

INI Dateien

Eines der ältesten Formate sind die bekannten INI Dateien. Sie speichern Informationen nach dem Schlüssel = Wert Prinzip. Wenn ein Schlüssel in solch einer INI-Datei mehrfach vorkommt, wird der finale Wert immer durch den zuletzt in der Datei vorkommenden Wert überschrieben.

; Example of an INI File
[Section-name]
key=value ; inline 

text="text configuration with spaces and \' quotas"
string='can be also like this'
char=passwort

# numbers & digets
number=123
hexa=0x123
octa=0123
binary=0b1111
float=123.12

# boolean values
value-1=true
value-0=false

Wie wir in dem kleinen Beispiel sehen können, ist die Syntax in INI-Dateien sehr einfach gehalten. Der Sektionsname [section] dient vor allem der Gruppierung einzelner Parameter und verbessert die Lesbarkeit. Kommentare können entweder durch ; oder # gekennzeichnet werden. Ansonsten gibt es die Möglichkeit, verschiedene Text- und Zahlen-Formate, sowie Boolean-Wert zu definieren.

Web-Entwickler kennen INI Files vor allem von der PHP-Konfiguration, der php.ini in der wichtige Eigenschaften wie die Größe des Datei-Uploads festgelegt werden können. Auch unter Windows sind INI-Dateien noch immer verbreitet, obwohl seit Windows 95 für diesen Zweck die Registry eingeführt wurde.

Properties

Eine andere sehr bewährte Lösung sind sogenannte property Files. Besonders verbreitet ist diese Lösung in Java-Programmen, da Java bereits eine einfache Klasse mitbringt, die mit Properties umgehen kann. Das Format key=value ist den INI-Dateien entlehnt. Kommentare werden ebenfalls mit # eingeleitet.

# PostgreSQL
hibernate.dialect.database = org.hibernate.dialect.PostgreSQLDialect
jdbc.driverClassName = org.postgresql.Driver 
jdbc.url = jdbc:postgresql://127.0.0.1:5432/together-test

Um in Java-Programmen beim Einlesen der .propreties auch die Typsicherheit zu gewährleisten, hat die Bibliothek TP-CORE eine erweiterte Implementierung. Trotz dass die Properties als Strings eingelesen werden, kann auf die Werte mittels Typisierung zugegriffen werden. Eine ausführliche Beschreibung, wie die Klasse PropertyReader verwendet werden kann, findet sich in der Dokumentation.

Auch im Maven Build Prozess können .property Dateien als Filter für Substitutionen genutzt werden. Selbstredend sind Properties nicht nur auf Maven und Java beschränkt. Auch in Sprachen wie Dart, nodeJS, Python und Ruby ist dieses Konzept nutzbar. Um eine größtmögliche Kompatibilität der Dateien zwischen den verschiedenen Sprachen zu gewährleisten, sollten exotische Optionen zur Notation vermieden werden.

XML

XML ist seit vielen Jahren auch eine weitverbreitete Option, Konfigurationen in einer Anwendung veränderlich zu speichern. Gegenüber INI und Property Dateien bietet XML mehr Flexibilität in der Definition der Daten. Ein sehr wichtiger Aspekt ist die Möglichkeit, fixe Strukturen durch eine Grammatik zu definieren. Dies erlaubt eine Validierung auch für sehr komplexe Daten. Dank der beiden Prüfmechanismen Wohlgeformtheit und Datenvalidierung gegen eine Grammatik lassen sich mögliche Konfigurationsfehler erheblich reduzieren.

Bekannte Einsatzszenarien für XML finden sich beispielsweise in Java Enterprise Projekten (J EE) mit der web.xml oder der Spring Framework und Hibernate Konfiguration. Die Mächtigkeit von XML gestattet sogar die Nutzung als Domain Specific Language (DSL), wie es bei dem Build-Werkzeug Apache Maven zum Einsatz kommt.

Dank vieler frei verfügbarer Bibliotheken existiert für nahezu jede Programmiersprache eine Implementierung, um XML-Dateien einzulesen und gezielt auf Daten zuzugreifen. Die bei Web-Entwicklern beliebte Sprache PHP hat zum Beispiel mit der Simple XML Erweiterung eine sehr einfache und intuitive Lösung, um mit XML umzugehen.

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" 
         xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/assembly/ApplicationContext.xml</param-value>
    </context-param>
    <context-param>
        <param-name>javax.faces.PROJECT_STAGE</param-name>
        <param-value>${jsf.project.stage}</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>

    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>

    <welcome-file-list>
        <welcome-file>index.xhtml</welcome-file>
    </welcome-file-list>
</web-app>

JavaScript Object Notation oder kurz JSON ist eine vergleichsweise neue Technik, obwohl diese mittlerweile auch schon einige Jahre existiert. Auch JSON hat für nahezu jede Programmiersprache eine entsprechende Implementierung. Das häufigste Einsatzszenario für JSON ist der Datentausch in Microservices. Der Grund liegt in der Kompaktheit von JSON. Gegenüber XML ist der zu übertragene Datenstrom in Webservices wie XML RPC oder SOAP mit JSON aufgrund der Notation wesentlich geringer.

Ein signifikanter Unterschied zwischen JSON und XML besteht aber auch im Bereich der Validierung. Grundsätzlich findet sich auf der offiziellen Homepage [1] zu JSON keine Möglichkeit, eine Grammatik wie in XML mit DTD oder Schema zu definieren. Auf GitHub existiert zwar ein Proposal zu einer JSON-Grammatik [2] hierzu fehlen aber entsprechende Implementierungen, um diese Technologie auch in Projekten einsetzen zu können.

Eine Weiterentwicklung zu JSON ist JSON5 [3], das bereits 2012 begonnen wurde und als Spezifikation in der Version 1.0.0 [4] seit dem Jahr 2018 offiziell veröffentlicht ist. Zweck dieser Entwicklung war es, die Lesbarkeit von JSON für Menschen erheblich zu verbessern. Hier wurden wichtige Funktionen, wie beispielsweise die Möglichkeit, Kommentare zu schreiben, hinzugefügt. JSON5 ist als Erweiterung vollständig zu JSON kompatibel. Um einen kurzen Eindruck zu JSON5 zu gewinnen, hier ein kleines Beispiel:

{
  // comments
  unquoted: 'and you can quote me on that', 
  singleQuotes: 'I can use "double quotes" here',
  lineBreaks: "Look, Mom! \
No \\n's!",
  hexadecimal: 0xdecaf,
  leadingDecimalPoint: .8675309, andTrailing: 8675309.,
  positiveSign: +1,
  trailingComma: 'in objects', andIn: ['arrays',],
  "backwardsCompatible": "with JSON",
}

YAML

Viele moderne Anwendungen, wie zum Beispiel YAML, zur Konfiguration. Die sehr kompakte Notation erinnert stark an die Programmiersprache Python. Aktuell ist YAML in der Version 1.2 veröffentlicht.

Der Vorteil von YAML gegenüber anderen Spezifikationen ist die extreme Kompaktheit. Gleichzeitig besitzt die Version 1.2 eine Grammatik zu Validierung. Trotz der Kompaktheit liegt der Fokus von YAML 1.2 in einer guten Lesbarkeit für Maschinen als auch Menschen. Ob YAML dieses Ziel erreicht hat, überlasse ich jedem selbst zu entscheiden. Auf der offiziellen Homepage findet man alle Ressourcen, die für eine Verwendung im eigenen Projekt benötigt werden. Dazu zählt auch eine Übersicht zu den vorhandenen Implementierungen. Das Design der YAML Homepage gibt auch schon einen guten Vorgeschmack auf die Übersichtlichkeit von YAML Dateien. Anbei noch ein sehr kompaktes Beispiel einer Prometheus Konfiguration in YAML:

global:
  scrape_interval:     15s
  evaluation_interval: 15s

rule_files:
  # - "first.rules" 
  # - "second.rules"

#IP: 127.0.0.1
scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: ['127.0.0.1:8080']

  # SPRING BOOT WEB APP
  - job_name: spring-boot-sample 
    scrape_interval: 60s
    scrape_timeout: 50s
    scheme: "http"
    metrics_path: '/actuator/prometheus' 
    static_configs:
     - targets: ['127.0.0.1:8888']
    tls_config:
     insecure_skip_verify: true

Resümee

Alle hier vorgestellten Techniken sind im praktischen Einsatz in vielen Projekten erprobt. Sicher mag es für spezielle Anwendungen wie REST Services einige Präferenzen geben. Für meinen persönlichen Geschmack bevorzuge ich für Konfigurationsdateien das XML Format. Dies ist leicht im Programm zu verarbeiten, extrem flexibel und bei geschickter Modellierung auch kompakt und hervorragend für Menschen lesbar.

Referenzen

Abonnement / Subscription

[English] This content is only available to subscribers.

[Deutsch] Diese Inhalte sind nur für Abonnenten verfügbar.


Ruby: die Entwicklungsumgebung einrichten

Ruby ist seit vielen Jahren eine sehr etablierte Programmiersprache, die durchaus auch Anfängern empfohlen werden kann. Ruby folgt dem objektorientierten Paradigma und enthält sehr viele Konzepte, um OOP gut zu unterstützen. Außerdem lassen sich dank des Frameworks Ruby on Rails unkompliziert komplexe Webanwendungen entwickeln.

Die schwierigste Hürde beim Einstieg in Ruby, die es zu meistern gilt, ist die Installation der gesamten Entwicklungsumgebung. Angesichts dessen habe ich dieses kurze Tutorial zum Einstieg mit Ruby verfasst. Beginnen wir daher auch gleich mit der Installation.

Mein Betriebssystem ist ein Debian 12 Linux und Ruby lässt sich sehr einfach mit der Anweisung sudo apt-get install ruby-full installieren. Dieses Vorgehen kann auf alle Debian basierte Linux Distributionen wie z. B. Ubuntu übertragen werden. Anschließend kann mit ruby -v der Erfolg in der Bash überprüft werden.

ed@:~$ ruby -v 
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux-gnu]


Wenn wir nun dem Tutorial auf der Ruby on Rails Homepage folgen und das Rails Framework über gem rails installieren wollen, stoßen wir bereits auf das erste Problem. Wegen fehlender Berechtigungen lassen sich keine Bibliotheken für Ruby installieren. Nun konnten wir auf die Idee kommen, die Bibliotheken, als Superuser mit sudo zu installieren. Dies Losung ist leider nur temporär und verhindert, dass später in der Entwicklungsumgebung die Bibliotheken korrekt gefunden werden. Besser ist es, einen Ordner für die GEMs im home Verzeichnis des Nutzers anzulegen und dies über eine Systemvariable bereitzustellen.

export GEM_HOME=/home/<user>/.ruby-gems
export PATH=$PATH:/home/<user>/.ruby-gems

Die oben stehende Zeile ist an das Ende der Datei .bashrc einzutragen, damit die Änderungen auch persistent bleiben. Wichtig ist, dass <user> gegen den richtigen Nutzernamen ausgetauscht wird. Den Erfolg dieser Aktion lasst sich über gem environment überprüfen und sollte eine ähnliche Ausgabe wie nachstehend ergeben.

ed@:~$ gem environment
RubyGems Environment:
  - RUBYGEMS VERSION: 3.3.15 
  - RUBY VERSION: 3.1.2 (2022-04-12 patchlevel 20) [x86_64-linux-gnu]
  - INSTALLATION DIRECTORY: /home/ed/.ruby-gems
  - USER INSTALLATION DIRECTORY: /home/ed/.local/share/gem/ruby/3.1.0
  - RUBY EXECUTABLE: /usr/bin/ruby3.1
  - GIT EXECUTABLE: /usr/bin/git
  - EXECUTABLE DIRECTORY: /home/ed/Programs/gem-repository/bin
  - SPEC CACHE DIRECTORY: /home/ed/.local/share/gem/specs
  - SYSTEM CONFIGURATION DIRECTORY: /etc
  - RUBYGEMS PLATFORMS:
     - ruby
     - x86_64-linux
  - GEM PATHS:
     - /home/ed/Programs/gem-repository
     - /home/ed/.local/share/gem/ruby/3.1.0
     - /var/lib/gems/3.1.0
     - /usr/local/lib/ruby/gems/3.1.0
     - /usr/lib/ruby/gems/3.1.0
     - /usr/lib/x86_64-linux-gnu/ruby/gems/3.1.0
     - /usr/share/rubygems-integration/3.1.0
     - /usr/share/rubygems-integration/all
     - /usr/lib/x86_64-linux-gnu/rubygems-integration/3.1.0
  - GEM CONFIGURATION:
     - :update_sources => true
     - :verbose => true
     - :backtrace => false
     - :bulk_threshold => 1000
  - REMOTE SOURCES:
     - https://rubygems.org/
  - SHELL PATH:
     - /home/ed/.local/bin
     - /usr/local/bin
     - /usr/bin
     - /bin
     - /usr/local/games
     - /usr/games
     - /snap/bin
     - /home/ed/Programs/maven/bin
     - /usr/share/openjfx/lib
- /home/ed/.local/bin

Mit dieser Einstellung lassen sich nun ohne Schwierigkeiten Ruby GEMs installieren. Das probieren wir auch gleich aus und installieren, das Ruby on Rails Framework, was uns bei der Entwickelung von Webapplikationen unterstützt: gem install rails. Dies sollte indessen ohne Fehlermeldungen durchlaufen und mit dem Kommando rails -v sehen wir, ob wir erfolgreich waren.

Im nächsten Schritt können wir nun ein neues Rails Projekt anlegen. Hier bediene ich mich dem Beispiel aus der Ruby on Rails Dokumentation und schreibe in die Bash: rails new blog. Dies erzeugt ein entsprechendes Verzeichnis namens blog mit den Projektdateien. Nachdem wir in das Verzeichnis gewechselt sind müssen wir noch alle Abhängigkeiten installieren. Das geschieht über: bundle install.

Hier stoßen wir nun auf ein weiteres Problem. Die Installation kann nicht beendet werden, weil es anscheinend ein Problem mit der Bibliothek psych gibt. Das tatsächliche Problem ist allerdings, dass auf der Betriebssystemebene die Unterstützung für YAML-Dateien fehlt. Das lässt sich rasch beheben, indem das YAML-Paket nachinstalliert wird.

sudo apt-get install libyaml-dev

Das Problem mit psych bei Ruby on Rails besteht schon eine Weile und ist mit der YAML Installation behoben, so, dass nun auch die Anweisung bundle install erfolgreich durchläuft. Jetzt sind wir auch in der Lage, den Server für die Rails Anwendung zu starten: bin/rails server.

ed@:~/blog$ bin/rails server
=> Booting Puma
=> Rails 7.1.3.3 application starting in development  
=> Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 6.4.2 (ruby 3.1.2-p20) ("The Eagle of Durango")
*  Min threads: 5
*  Max threads: 5
*  Environment: development
*          PID: 12316
* Listening on http://127.0.0.1:3000
* Listening on http://[::1]:3000
Use Ctrl-C to stop

Rufen wir nun im Webbrowser die URL http://127.0.0.1:3000 auf, sehen wir unsere Rails Webanwendung.

Mit diesen Schritten haben wir nun eine funktionierende Ruby Umgebung auf unserem System erstellt. Nun ist es an der Zeit, sich für eine geeignete Entwicklungsumgebung zu entscheiden. Wer nur gelegentlich ein paar Scripte anpasst, dem genügen VIM und Sublime Text als Editoren. Für komplexe Softwareprojekte sollte wiederum auf eine vollständige IDE zurückgegriffen werden, da dies die Arbeit erheblich vereinfacht. Die beste Empfehlung ist die kostenpflichtige IDE RubyMine von JetBrains. Wer Ruby Open Source Projekte als Entwickler unterstützt, kann eine kostenfreie Lizenz beantragen.

Eine frei verfügbare Ruby IDE ist VSCode von Microsoft. Hier müssen aber zunächst ein paar Plug-ins eingebunden werden, und für meinen Geschmack ist VSCode auch nicht sehr intuitiv. Ruby Integration für die klassischen Java IDEs Eclipse und NetBeans sind recht veraltet und nur mit sehr viel Mühe zum Laufen zu bekommen.

Damit haben wir auch schon alle wichtigen Punkte, die notwendig sind, eine funktionierende Ruby-Umgebung auf dem eigenen System einzurichten, besprochen. Ich hoffe, mit diesem kleinen Workshop die Einstiegshürde zum Erlernen von Ruby erheblich gesenkt zu haben. Wenn Ihr diesen Artikel mögt, freue ich mich über ein Like und die Weiterempfehlung an eure Freunde.

Entwickler Tage remote 2024

Test Driven: from zero to hero

In der Softwarebranche ist es üblich, dass die Codebasis eine ausreichende Testautomatisierung aufweist. Denn dies ist für einen stabilen DevOps-Prozess und sicheres Refactoring notwendig. Aber die Realität sieht oft ganz anders aus. Fast jedes Projekt, an dem ich während meiner Karriere teilgenommen habe, hatte keine Zeile Testcode. Wenn wir darüber nachdenken, dass seit über 40 Jahren immer noch 80% aller kommerziellen Softwareprojekte scheitern, sollte uns das nicht überraschen. Aber das muss nicht so sein. In diesem Vortrag zeige ich, wie einfach es ist, auch in großen Projekten einen testgetriebenen Ansatz einzuführen. Das technische Setup ist ein Standard-Java-Projekt mit Apache Maven und JUnit 5.


Modern Times

Die meisten DevOps-Teams sind überzeugt, dass die Automatisierung selbst und die Automatisierung selbst eine große Herausforderung darstellt. Es scheint dringend notwendig zu sein, alles zu automatisieren – sogar die Automatisierung selbst. Dies ist für die meisten DevOps-Teams ein allgemeines Verständnis und somit Motivation. Werfen wir einen Blick auf typische, kontinuierliche Dummheiten bei der Transformation vom reinen Konfigurationsmanagement zum DevOps-Engineer.

Die meisten DevOps-Teams sind davon überzeugt, dass die Automatisierung selbst und die Automatisierung selbst eine große Herausforderung darstellt. Es scheint dringend notwendig zu sein, alles zu automatisieren – sogar die Automatisierung selbst. Dies ist für die meisten DevOps-Teams ein allgemeines Verständnis und somit Motivation. Werfen wir einen Blick auf typische, kontinuierliche Dummheiten bei der Transformation vom reinen Konfigurationsmanagement zum DevOps-Engineer.

  1. Build-Logik kann eine fehlerhafte Architektur nicht reparieren. Zahlreiche SCM-Zusammenführungskonflikte entstehen durch die fehlende Kapselung der Geschäftslogik. Bei Funktionen, die über viele Module oder Dienste verteilt sind, ist die Wahrscheinlichkeit hoch, dass eine Datei von mehreren Entwicklern bearbeitet wird.
  2. Die Notwendigkeit orchestrierter Builds deutet auf Architekturprobleme hin. Transitive Abhängigkeiten, fehlende Kapselung und eine umfangreiche Abhängigkeitskette sind typische Gründe für das Henne-Ei-Problem. Entwerfen Sie Ihre Artefakte so unabhängig wie möglich.
  3. Build-Logik wird von Entwicklern und nicht von Administratoren entwickelt. Personen mit Fokus auf den Betrieb haben andere Konzepte zur Pflege von Artefakt-Builds als Softwareentwickler. Ein gutes Beispiel für eine Anti-Pattern-Build-Struktur sind webMethods der Software AG. webMethods bietet keinen Repository-Server wie Sonatype Nexus zum Teilen von Abhängigkeiten. Der Build verweist immer auf die Abhängigkeiten innerhalb einer webMethods-Installation. Diese Vorgehensweise verstößt gegen die Grundidee der Build-Automatisierung, die im Buch „Praktiken eines agilen Entwicklers“ von 2006 beschrieben wird.
  4. Nicht alles auf einmal. Teilen Sie die Build-Jobs in konkrete Ziele auf, wie z. B. Artefakt erstellen, Abnahmetests durchführen, API-Dokumentation erstellen und Berichte generieren. Wenn einer der letzten Schritte fehlschlägt, müssen Sie nicht alles wiederholen. Die Ausführungszeit des Builds wird drastisch reduziert, und die Build-Infrastruktur lässt sich leichter warten.
  5. Geben Sie Ihrer Build-Infrastruktur nicht zu viel Flexibilität. Dieser Punkt hängt eng mit dem ersten Thema zusammen, das ich erläutert habe. Ein undisziplinierter Build-Manager erstellt extrem komplexe Skripte, die niemand versteht. Der JavaScript-Task-Runner Grunt ist ein Beispiel dafür, wie eine Build-Logik unübersichtlich und unleserlich werden kann. Dies ist einer der Gründe, warum ich mich für Maven als Build-Tool für Java-Projekte entscheide, da es die Steuerung verständlicher Builds ermöglicht.
  6. Es besteht keine Notwendigkeit, die Automatisierung zu automatisieren. Komplexe Automatisierungsstufen verursachen per Definition höhere Kosten als einfache Aufgaben. Überlegen Sie sich immer vorher, welchen Nutzen Ihre Automatisierungsaktivitäten bringen, um zu prüfen, ob es sich lohnt, Zeit und Geld dafür aufzuwenden.
  7. Wir tun, was wir können, aber können wir auch, was wir tun? Oder mit den Worten von Gardy Bloch: „A fool with a tool is still a fool.“ Verstehen Sie die Anforderungen Ihres Projekts und entscheiden Sie auf dieser Grundlage, welches Tool Sie wählen. Wenn Ihnen die Ressourcen fehlen, kann Sie selbst die professionellste Lösung nicht unterstützen. Wenn Sie Ihr Problem verstanden haben, können Sie neue, professionelle und fortgeschrittene Prozesse erlernen.
  8. Die Build-Logik wurde zunächst in der lokalen Entwicklungsumgebung ausgeführt. Wenn Ihr Build nicht auf Ihrem lokalen Entwicklungsrechner läuft, nennen Sie es nicht Build-Logik. Es ist nur ein Hack. Die Build-Logik muss plattform- und IDE-unabhängig sein.
  9. Vermischen Sie Quellcode-Repositories nicht. mit anderen Dateien Die Organisation der Quellen in mehreren Ordnern innerhalb eines riesigen Verzeichnisses führt zu einem komplexen Build ohne jegliche Flexibilität. Quellen sollten nach Technologie oder separaten, unabhängigen Modulen strukturiert sein.

Viele der genannten Punkte lassen sich anhand der aktuellen Situation in fast jedem Projekt nachvollziehen. Die Lösung für eine erfolgreiche Problembehebung ist meist nicht allzu kompliziert. Sie erfordert lediglich ein wenig Aufmerksamkeit und gute Planung. Mein wichtigster Ratschlag ist das KISS-Prinzip (Keep it simple, stupid). Das bedeutet, den Standardprozess so weit wie möglich unverändert zu übernehmen. Man muss das Rad nicht neu erfinden. Es gibt Gründe, warum ein Standard zum Standard wird. Hier ist ein kurzer Plan, dem Sie folgen können:

  • Erstens: Verstehen Sie das Problem.
  • Zweitens: Suchen Sie nach einer Standardlösung für den Prozess.
  • Drittens: Entwickeln Sie einen Plan zur Integration der Lösung in die bestehende Prozesslandschaft. Dies bedeutet, Tools zu entfernen, die Standardprozesse nicht unterstützen.

Wenn Sie Ihren eigenen Weg Schritt für Schritt gehen, ohne zu weit zu springen, können Sie schnell positive Ergebnisse erzielen.

Übrigens: Wenn Sie Unterstützung für einen erfolgreichen DevOps-Prozess benötigen, kontaktieren Sie mich gerne. Ich biete praktische Beratung und Schulungen zum Aufbau eines leistungsstarken DevOps-Teams an.