Microservices und die Jagd nach mehr Konversion – Fluch oder Segen für den Entwickler

Softwareentwicklung wandelt sich derzeit: Es reicht nicht mehr Produktinkremente potenziell produktiv zu setzen, sondern Änderungen müssen produktiv gehen. Um in gesättigten Märkten noch Innovationen hervorzubringen und neues Wachstum zu generieren, arbeiten Fachabteilungen immer öfter mit Lean-Startup-Methoden. Eine Methode im Lean-Startup ist die Bildung von Hypothesen, die durch Tests validiert werden. Dazu wird Software in unterschiedlichen Varianten ausgespielt und dann beobachtet, wie Kunden auf eine Änderungen reagieren. Je nach Test-Ergebnis werden Teile der Software dann wieder entfernt oder angepasst. Somit ist es wichtig Software schnell an den an den Anwender zu bringen.

Software schneller in Produktion zu bringen, ist mit den meisten Softwarearchitekturen nicht so einfach. Häufig existieren monolithische Anwendungen, die kompliziert zu installieren sind und bei denen für kleine Änderungen die Koordination von vielen Teams nötig ist. Um Teile der Software zu entkoppeln und Koordinationsaufwand zwischen den Teams zu verringern, wird häufig vorgeschlagen eine Applikation in kleine Dienste zu zerlegen, oft kurz Microservice-Architektur genannt.

Wir sehen uns an Hand eines konkreten Beispiels an, wie man eine bestehende Architektur in eine Mircroservice-Architektur überführen kann und welche Konsequenzen dies auf technischer Ebene hat. Zum Beispiel führen Microservices zu mehreren Deployment Pipelines und man braucht Konzepte die unterschiedlichen Teile der Software auch schnell auf unterschiedlichen Rechner provisionieren zu können. Außerdem sollte man sich Gedanken zum Thema Logging und dem Monitoring im Betrieb machen, denn sonst kann der Betrieb eines solchen hochverteilten Systems schnell zur Qual werden.

Folien

slides/zuther/slides/02_bobkonf/index.html
Quellcode
https://github.com/zutherb/AppStash/
Artikel in der Java-Aktuell 02/2015
Share Button

Deployment ganz einfach – Microservice Deployment mit Hilfe der Linux Paketverwaltung

Softwareentwicklung wandelt sich, anstatt Software für die nächsten Jahre zu planen, zu entwickeln und dann erst dem Kunden zu präsentieren, arbeiten Fachabteilungen oft mit kleinen Hypothesen. Diese Hypothesen werden mithilfe von A/B Tests direkt am Kunden validiert und kommen dann als neue Userstories in den Entwicklungsprozess. Damit werden schrittweise Unternehmensprozesse und bestehende Software optimiert. Durch dieses Vorgehen bauen Fachabteilung Software, die ihre Kunden wirklich wollen. Das stellt die IT-Abteilungen natürlich vor die Herausforderung öfter und schneller ausliefern zu müssen, um schneller von den Testergebnissen profitieren zu können und letztlich mehr Umsatz zu generieren.

Beispielsweise kann eine simple Änderung der Farbe eines Links in einem Online-Shop eine Konvertierungssteigerung von 20% auslösen. Ein solche Änderung ist relativ schnell umgesetzt, doch selbst mit Continuous Delivery und einer Deployment Pipeline kann, der Durchlauf bis in Produktion mehrere Stunden in Anspruch nehmen, was ziemlich viel Geld kosten kann. Es gibt deshalb Stimmen, die meinen, dass solche langen Durchlaufzeiten am architektonischen Aufbau der Anwendungen liegt.

Aufbau von Web-Anwendungen

Web-Anwendungen sind häufig aus den folgenden drei Teilen aufgebaut:

  • Einer Benutzerschnittstelle (z.B. HTML),
  • einer Datenbank und
  • einer Server-Komponente.

Die Server-Komponente kümmert sich um die Abarbeitung der HTTP Requests, führt die Domänenlogik aus, ließt und schreibt Daten aus/in die Datenbank, bestimmt welche Teile der  Benutzerschnittstelle ausgeliefert werden müssen. Jede Änderung in diesem System erfordert einen Build und ein Deployment einer neuen Version der kompletten Software. Solche Anwendungen werden auch Monolithen genannt.

Auch wenn Monolithen sehr erfolgreich sein können, gibt es eine steigende Anzahl von Leuten, die mit diesen Ansatz nicht zufrieden sind, weil man auch für kleine Änderungen sehr hohe Durchlaufzeiten bis in Produktion hat und die Entwicklungsteams nicht mehr richtig skalieren, da man immer wieder die komplexen und zeitaufwendigen Buildprozesse der Monolithen durchlaufen muss und Änderungen an der Software immer schwieriger werden. Außerdem skalieren Monolithen schlecht. Wenn man einen solchen Monolithen skalieren möchte, muss man immer die komplette Anwendung verteilen, auch wenn nur bestimmte Teile der Anwendung Performance-Probleme hervorrufen. 

Es gibt Stimmen, die meinen, dass die klassische Drei-Schichten nicht mehr gut genug skaliert, um kleine gewinnbringende Features schnell genug ausliefern zu können. Deshalb erfreut sich das Konzept der Microservice-Architektur immer größer Beliebtheit. Ziel ist es die Anwendung entsprechend ihrer Fachlichkeit zuzuschneiden und in kleine Services zu unterteilen, die getrennt von einander ausgeliefert werden können. Damit soll die Durchlauf eines Features bis in Produktion verringert werden.

Die Microservice-Architektur zerschneidet Anwendungen in eine Anzahl an Diensten. Diese Dienste sind eigenständig deploy- und skalierbar. Jeder Dienst stell eine abgrenzte Fachlichkeit bereit, kann in einer beliebigen Programmiersprache implementiert sein und von unterschiedlichen Teams bereitgestellt werden. Zerschneidet man eine Software in eigenständige Dienste erzeugt man natürlich eine Vielzahl an Deployment-Artifakten. Häufig hat der Betrieb aber schon Probleme mit dem Deployment einer einzelnen monolithischen Anwendung. Deshalb muss man einen Weg finden Abhängigkeiten besser aufzulösen, die für eine erfolgreiche Auslieferung benötigt werden.

Deployment von Web-Anwendungen

In der Regel werden Web-Anwendungen in einen Anwendungsserver aufgeliefert. Ein Anwendungsserver stellt einen Container dar,  der spezielle Dienste zur Verfügung stellt, wie beispielsweise Transaktionen, Authentifizierung oder den Zugriff auf Verzeichnisdienste, Webservices und Datenbanken über definierte Schnittstellen.

Um das Deployment in einem Anwendungsserver zu standardisieren gibt es das Web Archive oder das Enterprise Archive. Ein Web Archive ist ein Dateiformat, das beschreibt, wie eine vollständige Webanwendung nach der Java-Servlet-Spezifikation in eine Datei im JAR- bzw. ZIP-Format verpackt wird. Bei einem Enterprise Archive handelt es sich ebenfalls um eine Datei im JAR- bzw. ZIP-Format, die ein vollständiges Anwendungsprogramm – meist eine Webanwendung – gemäß dem Standard Java Platform, Enterprise Edition (Java EE) enthält. Leider reichen diese beiden Standards nicht aus, um alle Abhängigkeiten aufzulösen, die eine Anwendung für den fehlerfreien Betrieb benötigen.

Abhängigkeiten zwischen Anwendung und Anwendungsserver

Die Abbildung zeigt die Abhängigkeiten zwischen Anwendung und Anwendungsserver. Eine Anwendung hängt vom Anwendungsserver ab, da die Anwendung Bibliotheken und Infrastruktur des Anwendungsserver nutzt. Umgekehrt ist der Anwendungsserver aber auch von der Anwendung abhängig, da der Anwendungsserver für eine Anwendung speziell konfiguriert werden muss. Damit ist ein Anwendungsserver eigentlich Teil einer Anwendung und man sollte Anwendungsserver und Anwendung als eine Komponente sehen.

Benötigt eine neue Version der Anwendung auch einen neue Version des  Anwendungsserver gibt es für den Betrieb häufig viele manuelle Schritte auszuführen, weil diese Abhängigkeiten in eine WAR oder einem EAR nicht ausgedrückt werden können. Diese Abhängigkeiten werden dann oft textuell in Form eines Tickets beschrieben und wenn man Glück hat werden die Instruktionen vom Betrieb richtig ausgeführt. Ähnliches gilt bei Datenbankänderungen. Es wäre deshalb wünschenswert Deployment-Artifakte zu haben, die auch diese Abhängigkeiten ausdrücken können. Ein Beispiel für solche Deployment-Artifakte liefert Apple.

Deployment von Anwendungen über den App Store

IMG_1453Apple liefert pro Sekunde mehr als 800 Apps über den App Store aus (Quelle). Apple-typisch lädt man sich eine Anwendung herunter und nach einem Klick auf das Icon läuft die Anwendung immer sofort, ohne das der Benutzer noch irgend etwas machen muss.

Apple löst die Herausforderung eine Anwendung auszuliefern mit dem sogenannten Application Bundle. Native Mac OS X Applikationen sind mehr als nur eine einfache ausführbare Datei, auch wenn ein Benutzer im Finder immer nur ein einzelnes IcIMG_1449on sieht. Unter Mac OS X besteht eine Applikation Bundle aus einer Verzeichnisstruktur, die sowohl die ausführbaren Dateien als auch alle benötigen Ressourcen enthält, die von einer Applikation benötigt werden. Diese Struktur ist von Apple genau definiert.

Verzeichnis-Struktur eines Applikation Bundles
Verzeichnis-Struktur eines Applikation Bundles

Ein Application Bundle enthält die folgenden Verzeichnisse und Dateien:

  • Eine Info.plist Datei im Contents Verzeichnis, die wichtige Meta-Informationen enthält, die von Mac OS X benutzt werden, um die Anwendung zu starten. (“Java Dictionary Info.plist Keys”)
  • Außerdem sollte eine Datei mit dem Namen PkgInfo in dem Contents Verzeichnis enthalten sein. (“Data Type Registration”)
  • Die Icons, die im Dock und im Finder angezeigt werden, befinden sich im Resources Verzeichnis.
  • Im Resources Verzeichnis werden außerdem Dateien abgelegt, die für die Ausführung benötigt werden.
  • Im MacOS Verzeichnis befinden sich die ausführbaren Dateien.

Wenn man eine Anwendung im App Store veröffentlichen möchte, müssen alle Ressourcen, die von der Anwendung benötigt werden mit der Anwendung ausgeliefert werden. Ansonsten wird die Anwendung von Apple abgelehnt und kann nicht veröffentlich werden. Dieses Konzept scheint sich wunderbar zu eignen um einen Microservice auszuliefern. Meistens hat man in Produktion aber keine Apple Server. Deshalb wäre es wünschenswert ein alternativ Konzept dafür zu haben. Häufig werden Webanwendungen auf einen Linux-Server ausgeliefert. Daher hat die Anwendung noch eine zusätzliche Abhängigkeit zu Infrastruktur und diese Abhängigkeit kann man benutzen, um die Anwendung auszuliefern.

Linux-Paketverwaltung

Die meisten Linux Distributionen verfügen über eine Software-Paketverwaltung. Eine Software-Paketverwaltung ermöglicht die komfortable Verwaltung von Software, die in Paketform vorliegt. Dazu gehören das Installieren, Aktualisieren und Deinstallieren der Software.

Voraussetzung für Paket-Management ist, dass die zu installierende Software als entsprechendes Paket vorliegt. Ein solches Paket wird meistens von einem Betriebssystemanbieter erstellt, angeboten und gepflegt. Software wird meistens verteilt in unterschiedlichen Verzeichnissen des System installiert. Diese Verzeichnisse sind im Filesystem Hierarchy Standard definiert.

Änderungen, welche die Paketverwaltung zur Installation des Pakets am System vornehmen muss, werden von der Paketverwaltung aus dem Paket ausgelesen und umgesetzt. Erkennt die Paketverwaltung dabei, dass noch weitere Software für das Funktionieren benötigt wird (sogenannte Abhängigkeit, z. B. eine Programmbibliothek), aber noch nicht installiert ist, warnt sie entweder oder versucht, die fehlende Software mit den ihr zur Verfügung stehenden Mitteln, z. B. aus einem Repository, nachzuladen und vorweg zu installieren.

Konkrete Beispiele für Programme um Software-Pakete zu installieren sind:

  • Das Programm RPM, dass Pakete vom Typ *.rpm installieren und löschen kann  und
  • das Programm Dpkg, dass Pakete vom Typ *.deb installieren und löschen kann.

Beide können aber keine Abhängigkeiten auflösen, da sie keine Funktionen haben, um Software nachzuladen. Dies können höhere Schichten der Paketverwaltungen wie YUM, APT, pkg-get und andere. Mit YUM oder APT ist es aber somit möglich auch die Abhängigkeiten zwischen Anwendungsserver und Anwendung aus zu drücken. Da ich häufig Ubuntu verwende und ich deshalb mit Deb-Paketen besser vertraut bin, möchte ich diese im folgenden Besprechen. Die Konzepte für Rpm-Pakete sind allerdings ähnlich.

Aufbau ein Deb-Paket

Jedes Deb-Paket besteht aus drei Dateien, die mittels des UNIX-Kommandos ar oder dem debianspezifischen Kommando dpkg-deb entpackt werden können:

  • debian-binary: eine Textdatei mit der Versionsnummer des verwendeten Paketformats.
  • control.tar.gz: ein gepacktes Archiv, dass Dateien enthält, die zur Installation dienen oder Abhängigkeiten auflisten. Hier werden nur ein paar Beispiele aufgeführt. Weiterführende Beschreibungen dazu finden sich z. B. in der Offiziellen Debian FAQ zu .deb Paketen.
    • control enthält eine Kurzbeschreibung des Paketes sowie weitere Informationen, wie dessen Abhängigkeiten.
    • md5sums enthält MD5-Prüfsummen aller im Paket enthaltenen Dateien, um Verfälschungen erkennen zu können.
    • conffiles listet die Dateien des Paketes auf, die als Konfigurationsdateien behandelt werden sollen.
    • preinst, postinst, prerm, postrm sind optionale Skripte, die vor oder nach dem Installieren, Aktualisieren oder Entfernen des Pakets ausgeführt werden. Sie werden mit den Rechten des Nutzers root ausgeführt.
  • data.* Ist ein mit komprimiertes Archiv und enthält die eigentlichen Programmdaten mit relativen, beim Stammverzeichnis beginnenden Pfaden.

Integration in das Buildsystem

Mit jdeb gibt es eine Bibliothek, die einen Ant task und ein Maven Plugin bereitstellt um Debian Pakete zu erstellen. Diese Bibliothek ist Plattform unabhängig und baut Debian Pakete ohne die Installation von irgendwelchen zusätzlichen Tools. 

Natürlich gibt es auch ein Gradle-Plugin das  Debian Pakete baut. Das Plugin basiert auch auf jdeb und stellt eine Brücke zwischen jdeb und Gradle dar. Das folgende Listing zeigt die Verwendung des Plugins zusammen mit dem Application Plugin:

apply plugin: 'application'
apply plugin: 'pkg-debian'
debian {
    packagename = "product"
    publications = ['mavenStuff']
    controlDirectory = "${projectDir}/debian/control"
    changelogFile = "${projectDir}/debian/changelog"
    outputFile = "${buildDir}/debian/${packagename}_${version}.deb"

    data {
        dir {
            name = "${projectDir}/debian/data"
            exclusions = ["**/.DS_Store", "changelog"]
        }
        dir {
            name = "${buildDir}/debian-data/"
            exclusions = ["**/.DS_Store"]
        }
        file {
            name = "${projectDir}/src/main/resources/application.conf"
            target = "etc/product/application.conf"
            mapper {
                fileMode = "755"
            }
        }
    }
}

task prepareDeb {
    dependsOn installApp

    copy {
        from "${buildDir}/install/"
        into "${buildDir}/debian-data/usr/share/"
    }
}

Zusammenfassung

Die Microservices-Bewegung erscheint auf den ersten Blick, wie das reaktive Manifest der Fachabteilung. Durch das parallele Bearbeiten von Fachlichkeiten kann das vorher monolithische Deployment von Anwendungen parallelisiert werden und die IT Abteilung schneller liefern. Dadurch erzeugt man aber eine Vielzahl an Deployment-Artifakten, die gemanagt werden müssen. Häufig ist ein monolithisches Deployment aber schon schwer genug. Da sehr viele Abhängigkeiten existieren. Sehr oft werden Anwendungen auf Linuxsystemen ausgeliefert. Diese Abhängigkeit zur Infrastruktur kann man aber auch ausnutzen, in dem man die Paketverwaltung der Linuxsysteme nutzt, um die Anwendungen auszuliefern, man baut dann nicht einfach nur ein Web Archive (WAR Dateien), sondern liefert ein Linux-Paket aus, darin ist alles enthalten, was ein Dienst benötigt, um ordnungsgemäß ausgeführt werden zu können. Die Paketverwaltung löst dann alle Abhängigkeiten auf, damit eine Anwendung/Dienst läuft und führt alle nötigen Schritte zur Installation der Software automatisiert aus. Damit erhält man auf den Linuxsystemen ein Apple ähnliches Deployment und kann sich eine Art App Store in Form eines Repository aufbauen, das die Auslieferung von Software stark vereinfach. Theoretisch ist durch dieses Vorgehen möglich seinen kompletten IT-Stack einfach nur mit einen Befehl auszuliefern , führt man z.B. „apt-get upgrade“ aus, werden alle  Pakete wenn möglich auf verbesserte Version aktualisiert.

devops2

Share Button

JUGM Talk: Grünes Licht für Continuous Delivery – Bau dein eigenes Extreme Feedback Device


Share Button

Gradle: Erstellen von einem Java Application Bundle unter Mac OS X

Im letzten Artikel „Von Continuous Integration zu Continuous Delivery“ habe ich mich mit den Themen Continuous Integration und Continuous Delivery beschäftigt. In diesem Artikel wird diese Themen weitergeführt: Ich zeige, wie eine Java Applikation unter Mac OS X in ein sogenanntes Application Bundle umgewandelt wird. Als Application Bundle verhält sich eine Java Applikation wie eine normale native Mac OS X Applikation. Das Erstellen eines Application Bundle stellt somit einen essentiellen Schritt in einer Deployment Pipeline dar, der vor der Auslieferung der Software durchgeführt werden sollte.

Native Mac OS X Applikationen sind mehr als nur eine einfache ausführbare Datei, wobei ein Benutzer immer nur ein einzelnes Icon im Finder sieht. Unter Mac OS X besteht eine Applikation aus einer Verzeichnisstruktur, die sowohl die ausführbaren Dateien als auch andere Ressourcen enthält, die von einer Applikation benötigt werden. Diese Verzeichnisstruktur wird Application Bundle genannt und die Struktur eines Application Bundles ist von Apple genau definiert. Ein solches Application Bundle vereinfacht die Auslieferung für den Entwickler und verbirgt die Anwendungsinterna vor den Benutzer. Ein Application Bundle kann ganz einfach vom Benutzer über Drag ’n‘ Drop installieren werden, was die Übertragbarkeit einer Applikation stark verbessert.

Verzeichnisstruktur eines Applikation Bundles

Die tatsächliche Verzeichnisstruktur eines Application Bundles wird im Finder verborgen, sobald ein Verzeichnis mit der Endung .app gesuffixt wird. Außer dem Suffix gibt es ein zusätzliches Attribut, dass sogenannte Bundle Bit, dass an einem Verzeichnis gesetzt wird. Die Kombination aus Suffix und Bundle Bit macht ein Verzeichnis zu einem Application Bundle. Die genaue Struktur eines Application Bundles wird in Abbildung 1 dargestellt.

Verzeichnis-Struktur eines Applikation Bundles
Abbildung 1: Verzeichnisstruktur eines Applikation Bundles

Ein Java Application Bundle enthält die folgenden Verzeichnisse und Dateien:

  • Eine Info.plist Datei im Contents Verzeichnis, die wichtige Meta-Informationen enthält, die von Mac OS X benutzt werden. („Java Dictionary Info.plist Keys“)
  • Außerdem sollte eine Datei mit dem Namen PkgInfo in dem Contents Verzeichnis enthalten sein. Bei dieser Datei handelt es sich um eine einfache Textdatei, die den String APPL enthält, auf den vier Buchstaben oder vier Fragezeichen folgen. („Data Type Registration“)
  • Die Icons, die im Dock und im Finder angezeigt werden, befinden sich im Resources Verzeichnis.
  • Der Java-Code wird auch im Resources Verzeichnis abgespeichert und befindet sich dort im Verzeichnis Java.
  • Im MacOS Verzeichnis befindet sich der sogenannter JavaApplicationStub, dabei handelt es sich um eine native Anwendung, die die JVM hochfährt.

Gradle Build

Die meisten Java Projekte verfügen über ein Buildsystem, dass das Bauen der Software automatisiert und damit den Aufbau einer automatisierten Deployment Pipeline vereinfacht. Ein sehr populäres Buildsystem ist Gradle, dass viele Open Source Projekte mittlerweile benutzen (Spring und Hibernate). Gradle ist ein Buildsystem, dass eine Groovy basierende Domain Specific Language (DSL) zur Beschreibung der Projekte verwendet. Damit sind Gradle-Skripte auch direkt ausführbarer Code.

Gradle wurde für Builds entworfen, die aus einer Vielzahl von Projekten bestehen, wobei Gradle die grundlegende Philosophie verfolgt, dass das Buildsystem dem Anwender neben sinnvollen Voreinstellungen, die auf verbreiteten Konventionen beruhen, möglichst viele Freiheiten lassen soll. Außerdem soll der Benutzer die Möglichkeit haben, solche Voreinstellungen zu überschreiben, um seine Projektbesonderheiten abbilden zu können. Gradles Konzept will damit die Flexibilität von Ant mit der „build-by-convention“-Strategie von Maven zusammenbringen.

Wie flexibel Gradle ist kann im den folgenden Listing abgelesen werden. Das Listing zeigt den Buildscript eines Gradle Plugins, dass weiter unten noch genauer beschrieben wird. Gradles Build-Konzept übernimmt die von Maven eingeführten Standardkonventionen („convention over configuration“) für die Verzeichnisstruktur von Projekten (Abbildung 2). Wie man im Buildscript sieht muss man die Verzeichnisse, in denen sich der Quellcode befindet, nicht explizit angeben, um das Plugin zu bauen, sondern es wird einfach das src/main/groovy Verzeichnis verwendet, wie es die Mavenkonventionen vorgegeben.

apply plugin: 'groovy'
apply plugin: 'idea'

repositories {
    mavenCentral()
}

dependencies {
    compile gradleApi()
    compile localGroovy()
}

task compileJavaAppLuncher(type: Exec) {
    def javaAppLuncherDir = "${buildDir}/resources/main/com/github/zutherb/gradle/mapAppBundle"
    mkdir javaAppLuncherDir
    executable  "gcc"
    args        "-I", "/Library/Java/JavaVirtualMachines/jdk1.7.0_45.jdk/Contents/Home/include",
                "-I", "/Library/Java/JavaVirtualMachines/jdk1.7.0_45.jdk/Contents/Home/include/darwin",
                "-o", "${javaAppLuncherDir}/JavaAppLauncher",
                "-framework", "Cocoa",
                "-arch", "x86_64",
                "-isysroot", "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk",
                "-mmacosx-version-min=10.7",
                "${projectDir}/src/main/object-c/main.m",
                "-v"
}

tasks.processResources.dependsOn(compileJavaAppLuncher)
Abbildung 2: Maven Verzeichnisstruktur
Abbildung 2: Maven Verzeichnisstruktur

Gradle besteht aus einem abstrakten Kern und einer Vielzahl von Plugins. Selbst die  Implementierung des Java-Builds basiert auf einem Java-Plugin. Mit dieser Architektur gewinnt Gradle die Möglichkeit, Buildprozesse für beliebige Software-Plattformen bewerkstelligen zu können und liefert dem Anwender die Möglichkeit, seine „nicht-konventionellen“ Vorstellungen dem Tool beizubringen. Gradle liefert von Hause aus eine Menge von Plugins mit, die neben Java Groovy-, Scala- und sogar C++- Projekte bauen können.

Leider verfügt Gradle nicht von Hause aus über ein Plugin Mac OS X Anwendungen paketieren, auch wenn Gradle über ein sehr gutes Application Plugin verfügt, das eine Applikation für Windows und Linux paketiert.

Erstellen eines Application Bundles mit Gradle

Um ein Application Bundle mit Gradle zu erstellen, gibt das Projekt: gradle-macappbundle. Es funktioniert analog zu dem Gradle Application Plugin. Lediglich der MainClassName muss konfiguriert werden und dann erstellt das Plugin die oben genannte Verzeichnisstruktur. Das Plugin paketiert die Anwendung danach sogar in einem dmg Datei. Dieses MacAppBundle Plugin funktioniert wirklich gut, doch leider unterstützt der JavaApplicationStub nur Java6. Java7 Programme können leider nicht mit dem JavaApplicationStub geöffnet werden, was sehr schade ist, wenn man eine neuer Java-Version benutzen will. Außerdem wird die JRE nicht mit in die Applikation gepackt, was gerade bei den neueren Java-Versionen zu Problem führen kann, da Java von Apple nicht mehr mit MacOS ausgeliefert wird.

Auf der Suche nach einer Alternative bin ich bei Oracle fündig geworden. Der Oracle Appbundler Task verfügt über einen JavaApplicationStub, der in diesem Projekt allerdings JavaAppLauncher genannt wird und mit Java7/8 verwendet werden kann. Beim JavaAppLauncher handelt es sich um ein einfaches Objective C Programm, dass das über JNI eine JVM hochfährt und dabei die Info.plist Datei auswertet. Außerdem wird die JRE mit in die Anwendung kopiert, womit sichergestellt ist, dass die Java Applikation unter OSX immer läuft, auch wenn kein Java auf dem Rechner installiert ist. Prinzipiell ist es in einem Gradle-Script möglich auf einen Ant-Script zu zu greifen, allerdings hält sich Oracle bei der Paketierung nicht an die Konventionen die Apple propagiert. Deshalb habe ich mich entschlossen, diese beiden Projekte miteinander zu verbinden und ein eigenes Plugin auf deren Basis zu erstellen.

Der Quellcode von meinem Plugin ist in meinem github Repository verfügbar und unterscheidet sich in der Verwendung nicht vom gradle-macappbundle. Es verfügt über die folgenden neun Tasks:

  1. configMacApp – konfiguriert die Default-Werte für das Plugin
  2. generatePlist – generiert die Info.plist Datei
  3. generatePkgInfo – generiert die PkgInfo Datei
  4. copyToResourcesJava – kopiert die Jars in das .app Verzeichnis
  5. copyJavaAppLauncher – kopiert den JavaAppLauncher in das .app Verzeichnis, der zum Starten von Java benutzt wird
  6. copyJavaRuntime – kopiert die JRE in das .app Verzeichnis
  7. runSetFile – führt das Kommando SetFile aus, dass das Bundle Bit setzt
  8. createApp – Aggregator Task für die Tasks 9/10
  9. codeSign – erstellt die digitale Signatur für die Anwendung
  10. createDmg – erstellt die .dmg Datei, die das .app Verzeichnis enthält

Damit verfügt das Plugin über die komplette Funktionalität vom gradle-macappbundle Plugin und dem Oracle Appbundler Task. Um es zu verwenden muss man nur das Jar auf den ClassPath des Buildscripts packen und das Plugin im Buildscript hinzufügen. Das folgende Listing zeigt, wie man das Plugin in seinen eigenen Buildscript verwenden kann.

apply plugin: 'macAppBundle'

macAppBundle {
    mainClassName = "com.example.myApp.Start"
    icon = "myIcon.icns"
}

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.github.zutherb.gradle:gradle-macappbundle:0.1'
    }
}

Wenn man das Plugin entsprechend konfiguriert hat, wird das Plugin automatisch von Gradle benutzt, wenn man das Projekt mit dem build – Task baut. D.h. bei jedem Build wird automatisch eine dmg Datei erstellt, die das komplette Application Bundle enthält.

Viel Spaß damit.

Share Button

Von Continuous Integration zu Continuous Delivery (Teil 1)

Continuous Delivery wird häufig als Weiterentwicklung von Continuous Integration angesehen. Die Idee einer Deployment Pipeline soll dabei helfen, den Schritt von Continous Integration zu Continuous Delivery zu vollziehen.

Deployment Pipeline
Abbildung 1: Deployment Pipeline

Abbildung 1 zeigt eine Deployment Pipeline. Den Anfang dieser Deployment Pipeline stellt das Entwicklungsteam dar, dass zusammen an einem Projekt arbeitet. Bei der Zusammenarbeit von mehreren Entwicklern kommt es häufig zu so genannten Integrationsfehler. Schlecht ist es, wenn diese Integrationsfehler erst spät entdeckt werden, denn in einer späten  Projektphase ist die Beseitigung dieser Fehler deutlich teuer als in einer frühen Phase. Deshalb ist es wichtig, dass nach jeder Code-Integration ein automatisierter Build läuft, der zum Einen überprüft ob sich die Software überhaupt bauen lässt und zum Anderen die Software auf Fehler überprüft. Dabei hilft ein sogenanntes CI-System.

Abbildung 2: Aufbau eines CI-Systems
Abbildung 2: Aufbau eines CI-Systems

Abbildung 2 verdeutlich den schematischen Aufbau eines typischen CI-Systems und den Kreislauf der in einem CI-System vollzogen wird. Das Team führt Änderungen durch. Diese Änderungen werden vom Team an ein Version Control System (VCS) übergeben. Ein Build-Server überprüft kontinuierlich das VCS und führt bei Änderungen einen Build durch. Dieser Build kann erfolgreich sein oder fehlschlagen. Nach einem Build wird das Ergebnis vom Build-Server archiviert und das Ergebnis dem Entwicklungsteam mitgeteilt.

Der Schwerpunkt eines solchen CI Systems liegt also klar beim Entwickler. Dieser kann zufrieden sein, wenn der CI-Server signalisiert, dass der letzte Commit erfolgreich war. Leider wird dabei der Benutzer komplett außen vor gelassen, der mehr an neuer Funktionalität interessiert sein wird. Deshalb muss die Software im nächsten Schritt zum Benutzer kommen. Hierzu ist ein Release nötigt. Ein solches Release stellt in der Regel eine große Herausforderung da, denn dabei sind viele Beteiligte involviert und häufig auch viele manuelle Schritte, was zu einer hohen Fehlerrate führt. Leider vermeiden viele Unternehmen deshalb häufig Releases, was aus Sicht eines Benutzer natürlich ärgerlich ist, da er auf viele Features lange warten muss.

Als begeisterter Software-Entwickler und Consultant kann ich mit diesem Missstand natürlich nicht leben. Deshalb möchte ich euch im Verlauf der Zeit Techniken vorstellen, um neue Features zeitnah und fortwährend nach der Fertigstellung bereitzustellen. Damit werde ich euch zeigen, wie ich in meinem täglichen Alltag das Prinzip Continous Delivery zum Leben erwecken lasse.

Share Button