3.7.2015 - Maven - pompedipom

Teil 1 Teil 2 Teil 3 Teil 4

Die ominöse pom.xml

Im letzten Post haben wir gesehen, dass eine minimale pom.xml genügt, um Maven zum Kompilieren eines Standard-Projekts zu bewegen. Allerdings kann dieses Projekt nicht wirklich viel. Insbesondere kann es keine Abhängigkeiten auflösen.

Und damit kommen wir zur Maven-Magie. Wenn unser Projekt von einer Library A abhängig ist, die wiederum von den Libraries B, C und D abhängig ist, und diese wieder von Noch weiteren Libraries, undsoweiter, dann ist das ziemlich mühsam. Meistens merkt man ja erst beim Debuggen des fertigen Programms, dass irgendein .jar fehlt und muss das mühsam suchen und installieren. Nur um dann herauszufinden, dass dieses Jar dann wieder ein anderes braucht, das wir ebenfalls noch nicht haben.

Bei Maven genügt es, die Abhängigkeit zu A zu deklarieren, und es holt dann selber alle Abhängigkeiten dazu. Damit erkärt sich auch, wieso Maven beim ersten Start erstmal das halbe Internet herunterlädt. Viele Projekte verwenden irgendwelche Apache-Libraries, und sei es nur Log4j oder commons-io. Und Apache Libraries sind berüchtigt dafür, dass sie weit miteinander verknüpft sind. EIne Apache Library zu verwenden bedeutet so über den Daumen gepeilt, , dass man zehn Libraries installieren muss.

Mit Maven ist das alles egal. Man deklariert die, die man selber braucht und Maven erledigt den Rest.

Und das macht man so:

        <dependencies>
          <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.36</version>
          </dependency>
        </dependencies>

Die einzelnen Elemente habe ich ja schon im letzten Post erklärt. Eine gute IDE wie Idea lädt die Library herunter, sobald man die “dependency” einträgt, und man kann sie sofort im Projekt verwenden.

Und woher weiss man die genauen Angaben zu einer Library?

Zum Beispiel, indem man zu Maven central geht und dort ins Suchfeld das einträgt, was man zu der gewünschten Library weiss. In unserem Fall zum Beispiel “mysql-connector”. Die Seite spuckt dann eine Liste mit allen Treffern aus, und wenn man auf die gewünschte Versionsbezeichnung, hier also ‘5.1.36’ klickt, dann kommt man zu einer Detail-Seite, in der man links unter “dependency information” die Angaben für pom.xml gleich fixfertig für copy&paste vorbereitet findet. Nicht wirklich schwierig, nicht wahr?

Woher und wohin?

Bevor ich weitere Bereiche der pom.xml betrachte erst mal ein kleiner Einschub: Woher holt maven die libraries? Und wohin gehen sie? Im Projektverzeichnis sind sie jedenfalls nicht.

Wie immer gilt hier “convention over configuration”: Es gibt Standards, die man ändern kann. Standardmässig sucht Maven Libraries in maven central und kopiert sie ins “local repository” (Aha! Das ist dieses ominöse Repository, dem wir schon bei mvn install begegnet sind). Dieses Repository befindet sich auf linux und Mac Rechnern standardmässig in ~/.m2, in anderen Betriebssystemen vermutlich irgendwo anders. Darin befindet sich ein weiterer Ordner mit dem überaus passenden Namen repository. Und darin befinden sich sämtliche Artefakte, die heruntergeladen oder lokal installiert wurden.

Ach ja: Mit “Artefakte” ist im Apache-Speak alles gmeint, was von einem Progamm erstellt wurde. Also vor allem Jars und andere Compilate. Wieso ein Quellcode-File kein “Artefakt” sein soll (es ist ja auch nicht auf Bäumen gewachsen, sondern wurde künstlich ertsellt), ist ein Geheimnis der Apachen, auf das man nicht unbedingt näher einzugehen braucht.

Wie auch immer. Die Struktur innerhalb des Repository ist:

[groupId]
   [artifactId]
       [version]
          (Dateien)

Wobei groupId hierarchisch geteilt sein kann. Also ch.rgw.webelexis würde so aussehen:

[ch]
  [rgw]
    [webelexis]
      [1.0.0]

Die Dateien im Versions-Verzeichnis sind mindestens ein <artifactId>-<version>.jar, und eine Datei namens <artifactId>-<version>.pom, welche sich als Kopie der pom.xml entpuppt. Dazu möglicherweise Prüfsummendateien etc.

Maven kopiert bzw, erstellt also alles im local repository, und die Abhängigkeiten im Projekt werden zu diesem local repository erstellt. Es ist daher nicht notwendig, die jars in jedes Projekt zu kopieren, das sie benötigt. Wie man ein Projekt so “deployt”, dass man es mitsamt allen Abhängigkeiten weitergeben kann, zeige ich später.

Wenn man gerade kein Internet hat, dann kann man maven anweisen, offline zu arbeiten, also nur mit diesem local repository.

mvn -o package

erstellt ein Package, ohne erst im Internet nachzusehen, ob es irgendeas upzudaten gibt. Das wird natürlich nur dann klappen, wenn alle benötigten Abhängigkeiten schon lokal vorhanden sind.

2.7.2015 - Maven - Einstieg

Teil 1 Teil 2 Teil 3 Teil 4

Eine Polemik für blutige Maven-Anfänger (und solche, die es werden wollen)

Disclaimer: Maven ist ein grossartiges Projekt und ein unglaublich mächtiges Werkzeug. Und die Apache Foundation ist von riesiger Bedeutung für die OpenSource Welt und hat enorm viel dazu beigetragen, dass unsere Software heute so leistungsfähig ist. Nur, leider erschliesst sich ihr Charme dem Unvorbereiteten nicht sofort. Und dieser Unvorbereitete wird sich möglicherweise in diesem Blogpost teilweise wiedererkennen.


Kontaktaufnahme

Ich gebe es zu: Ich war nie ein grosser Freund von Maven. Selber wäre ich nie auf die Idee gekommen, eine XML-Datei von einem halben Meter Länge zu tippen, nur um ein Programm zu kompilieren. Maven konnte schliesslich nichts, was ein gewöhnliches Shell-Script nicht auch gekonnt hätte, nicht wahr?

Doch immer mehr fremde Projekte basieren auf Maven. Entweder die spinnen alle, oder es muss doch was dran sein. Aber Maven macht es einem nicht leicht: Wann immer ich ein fremdes Projekt kompilieren wollte, und hoffnungsfroh “mvn build” eingab, produzierte das Ding eine gigantische Fehlermeldung, der man nach längerem Studium entnehmen konnte, dass “build” wohl keine ihm genehme “lifecycle phase” sei. Was auch immer das bedeutet. Allerdings bequemte es sich auch nicht, anzugeben, was ihm denn für lifecycle Phasen besser gefallen würden. Auch ein “mvn -help” hilft da nicht wirklich. Und ‘mvn’ ohne Parameter liefert ebenfalls nur ein paar Dezimeter Fehlermeldungen statt irgendwelcher hilfreicher Angaben (Wer auf die Idee kommt, maven wie empfohlen mit dem -X switch zu starten, bekommt als Belohnung ein paar Meter statt nur Dezimeter unverständlicher Meldungen an den Kopf geworfen).

Es half also nichts, man musste die Dokumentation lesen. Und wer die Apache Foundation kennt, der weiss, es gibt zu jedem Projekt tonnenweise Dokumentation, die sich vor allem dadurch auszeichnet, dass sie für einen Einsteiger ins jeweilige Projekt praktisch vollkommen unverständlich ist. So auch bei Maven. Immerhin kann man mit ein wenig Mühe die standardmässig vorhandenen erlaubten Funktionen, Verzeihung: lifecycle-phasen, herausfinden. Zum Beispiel mvn install.

Und auch hier bewahrheitet sich, was man von anderen Apache Projekten kennt: Das Kommando tut keineswegs das, was man von ihm erwartet. Wer Linux kennt, der erwartet von mvn install irgendwie dasselbe, wie von make install eines Linux-Pakets: Das Programm oder die Library wird kompiliert und ausführbereit im System installiert. Nichts da. Die Apache Dockumentation erläutert, hilfreich wie immer, dass das Projekt so ins “local repository” installiert wird. Ohne allerdings darauf einzugehen, wo dieses Repository ist und wozu man es braucht. Wer glaubt, so ein Maven-Repository sei irgendwie so etwas wie ein git-Repository, der täuscht sich, soviel sei schon einmal verraten.

Immerhin: mvn compile, mvn test und mvn package tun das, was man denkt. Allerdings mit so viel output, dass man kaum erkennt, ob es nun funktioniert hat, oder nicht. Und oft steht inmitten der drei Meter output irgendwo ganz unschuldig ein halber Meter voll mit “ERROR” Zeilen. Allerdings sieht man das erst nach langer Zeit, da Maven grundsätzlich zumindest beim ersten ‘compile’ erst mal das halbe Internet herunterlädt. Also

Güldene Regel 1: Maven niemals von einem teuren Netzwerk aus starten. Nur Flatrate

Erst wenn das Projekt schon mal erfolgreich gepackaged wurde, sind die Datenmengen kleiner. Aber Achtung: Jede unschuldige Versionsänderung des Hauptprogramms kann dazu führen, dass auch das halbe Internet in neuen Versionen heruntergeladen werden muss. Und irgendwie weiss man nie, wieso das so ist. Klar: Es hängt irgendwie mit diesem ominösen pom.xml zusammen, von dem es immer eins oder mehrere in einem Maven-Projekt gibt. Bloss: In diesem pom.xml steht niemals alles, was maven tut. Irgendetwas tut es heimlich. Manche IDEs geben einem die Möglichkeit, die “effective POM” eines Maven-Projekts anzusehen. Die ist dann plötzlich sehr, sehr viel länger, allerdings versteht der interessierte Einsteiger kaum, wieso das eigentlich so ist.

Verblüffenderweise ist das alles gar nicht mal so kompliziert, wenn man mal hinters Prinzip gekommen ist. Doch zunächst brauchen wir, um ein eigenes Testprojekt zu starten, eine IDE. Und hier kommt eine Werbeeinblendung:


Werbeeinblendung:

Als früherer Eclipse-Fan empfehle ich IntelliJ Idea. Eclipse wurde immer grösser und träger und wirft bei mir bei jedem Start eine Menge Fehlermeldungen. Ausserdem ist der Maven Support (und auch der JavaScript Support, aber das ist eine andere Geschichte) doch sehr bescheiden. Idea kostet zwar etwas und versprüht designmässig einen eher spröden Charme, ist aber flink und hilfreich sowohl bei pom.xml als auch bei Javascript.


Ein Maven-Projekt

Und das starten wir mit der

Güldenen Regel 2: Konvention vor Konfiguration

Der grösste Teil meiner Schwierigkeiten mit Maven kam daher, dass ich Mavern irgendwie dazu bringen wollte, meine bestehende Projektstruktur zu übernehmen. Natürlich geht das. Aber dann muss man sich mit pom.xml wirklich, wirklich auskennen. Wenn das nicht der Fall ist, macht am am Besten alles so, wie Maven es nunmal haben will. Und das bedeutet, die Projektstruktur hat gefälligst so auszusehen:

 [Top]
 - pom.xml
 - [src]
    - [main]
       - [java]
           (da kommen die Packages hin)
       - [resources]
            (da kommt alles hin, was nicht zu kompilieren ist)
    - [test]
        - [java]
            (da kommen die Testklassen hin)
        - [resources]
            (was nicht zu kompilieren ist und auch nicht ins Endprodukt muss)

Der Name des Hauptverzeichnisses (hier [Top]) ist frei wählbar und sollte der Projektname sein. Weitere Dateien und Verzeichnisse dürfen mit hinein, aber die hier gezeigten müssen existieren und müssen genau so heissen (ohne die eckigen Klammern). Unterhalb von main/java und test/java kann eine beliebig tiefe Struktur von Packages und Java-Quelldateien sein.

Und wenn man das sklavisch genau befolgt hat, dann wird man damit belohnt, dass ein mvn compile von [Top] aus das Projekt ohne weiteres Zutun kompiliert. Dazu erstellt es eine weitere Ordnerhierarchie [target] parallel zu [src]. Dort hinein kommen alle Kompilate und alle Ressourcen (welche einfach unmodifiziert kopiert werden).

Und wenn man dann noch mvn package eintippt, wird man mit einem fixfertigen jar-file belohnt (welches allerdings nicht startfähig ist).

Moment: Allerdings klappt das alles nur, wenn die pom.xml korrekt ist. Für den Anfang kann sie so aussehen:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>rgwch</groupId>
        <artifactId>webelexis-server</artifactId>
        <version>0.8.0</version>
     </project>

Verblüffend einfach, nicht wahr? Das ist der Zauber des ‘convention over configuration’. Maven geht ohne andere Anweisungen einfach mal davon aus, dass das Projekt der Konvention entspricht und braucht dann keine weiteren Angaben ausser:

  • groupId: Das sollte ein möglichst eindeutiger Bezeichner sein. Zum Beispiel ‘ch.webelexis’. Man darf aber auch etwas anderes wählen, zum Beispiel ‘rgwch’.

  • artifactId: Das ist die Bezeichnung des aktuellen Projektes innerhalb der in groupId genannten Hierarchie.

  • version: Sollte eigentlich selbsterklärend sein :)

3.6.2015 - Docker

Teil 1 Teil 2 Teil 3 Teil 4

Auch Docker gehörte zu den Tools, die ich schon lange in meiner “Todo” bzw. “ToLookAt” Schublade hatte. Jetzt hatte ich endlich Zeit und Lust dazu.

Installation

Wer einen Mac mit homebrew hat, ist fein raus:

    brew install boot2docker
    boot2docker init
    boot2docker up

fertig.

Für andere Betriebsysteme stellt Docker Installationsanleitungen bereit. Bottomline: Man braucht ein halbwegs aktuelles 64-Bit Betriebssystem.

Kleiner Crashkurs

  • docker images : Listet alle geladenen Images
  • docker pull image : Holt das genannte Image
  • docker run image : Erstellt und startet einen Container vom genannten Image (Lädt das Image ggf. zuerst herunter, falls lokal noch nicht vorhanden.
  • docker run -t -i image sh : Erstellt einen Container und startet ihn im interaktiven Modus mit Terminal
  • docker ps -a : Listet alle Container
  • docker rm container : löscht einen Container
  • docker rmi image : löscht ein image
  • docker start -t container : Einen bestehenden Container starten

Achtung Möglicher Denkfehler mit hohem Zeitverlustrisiko: Wenn Sie ein image mit docker run starten, darin eine Menge ändern, und es dann verlassen, sind Ihre Änderungen keinesegs im Image gespeichert, sondern nur im Container. Den können Sie erneut starten mit docker start . Wenn Sie stattdessen erneut docker run eingeben, erhalten Sie einen neuen Container, der nichts von Ihren Änderungen enthält. Sie können Änderungen in ein neues Image persistieren, indem Sie docker commit verwenden. Bottomline: Es lohnt sich, über den Unterschied zwischen Image und Container nachzudenken: Ein Image ist ein read-only Abbild eines lauffähigen Systems. Ein Container ist ein “zum Leben erweckter” Abzug eines Image. Dieser verändert das Image niemals, egal was man damit anstellt.

Beispiel: Einen git-server starten

Ziel: Wir wollen lokal vorhandene Git-Repositories übers Netz erreichbar machen. Wir möchten aber nicht mit SSH-Schlüsseln hantieren, da wir ohnehin alle kennen, die Zugriff auf unser Netzwerk haben. Unsere git-Repositories seien auf /srv/develop/git.

Ausführung:

1. `docker run -d -p 8080:80 -v /srv/develop/git:/opt/git rgwch/git-server`

Das war’s. Es gibt kein 2. und kein 3. Man kann z.B. das webelexis-Repository jetzt erreichen mit: git clone http:<ip-adresse des docker-hosts>:8080/git/webelexis

Ein kleines Problem Wir werden Änderungen so nicht dorthin pushen können, denn push lässt git nur bei “bare” repositories zu. Wir müssten also zunächst von unseren Arbeitsrepositories “bare”-repositories auf den Server erstellen: git clone --bare <original> <bare-repo> Wenn wir dem Git-Server dann diese bare-Repositories zur Verfügungs stellen, klappt sowohl pull also auch push wie erwartet. (Beim Push muss man wenn man nichts ändert, als Name und passwort jeweils “git” eingeben)

Git Server container selber bauen

Den Quellcode, mit dem dieser git-server erstellt werden kann, finden Sie hier: https://github.com/rgwch/docker-git. Wenn sie Docker schon installiert haben, können Sie Ihren eigenen git server so bauen:

    git clone https://github.com/rgwch/docker-git.git gitserver
    cd gitserver
    sudo docker build -t ihrname/git-server:tag .

Das ist alles. Für “ihrname/git-server:tag” können Sie sich was Eigenes einfallen lassen.

Werfen wir einen kurzen Blick auf “Dockerfile”, die Steuerugsdatei des Ganzen:

    FROM debian:latest
    MAINTAINER weirich@elexis.ch
    COPY runcontainer /usr/sbin/runcontainer
    RUN apt-get -y update && apt-get -y install nano gitweb lighttpd 
    ENV GIT_HTTP_EXPORT_ALL ""
    RUN mkdir /opt/git
    COPY config/gitweb.conf /etc/gitweb.conf
    COPY addgituser.pl /usr/sbin/addgituser.pl
    COPY config/lighttpd/ /etc/lighttpd
    RUN chmod +x /usr/sbin/runcontainer && chmod +x /usr/sbin/addgituser.pl
    WORKDIR /etc/lighttpd
    CMD ["/usr/sbin/runcontainer"]

Wir gehen also von einem existierenden Image namens ‘debian:latest’ aus. Dort installieren wir mit apt-get alles, was wir benötigen, kopieren ein paar Steuerfiles in den Container und weisen den Container am Ende mit CMD an, das mitgelieferte “runcontainer”-Script zu starten, wenn der Anwender das Image mit “Run” benutzt.

Runcontainer sieht so aus:

    TARGET_GID=$(stat -c "%g" /opt/git)
    TARGET_UID=$(stat -c "%u" /opt/git)
    groupadd -g $TARGET_GID -o gitgroup
    useradd -u $TARGET_UID -g $TARGET_GID -r gitserver
    
    git config --global user.email "info@gitserver.invalid"
    git config --global user.name "admin git server"

    /usr/sbin/addgituser.pl git git >/etc/lighttpd/.passwd
    /usr/sbin/lighttpd -D -f /etc/lighttpd/lighttpd.conf

Zuerst versucht es (meist erfolgreich),herauszufinden, unter welcher UID und GID das Verzeichnis /opt/git läuft. Das ist ja das Verzeichnis, das wir dem container mit “sudo docker run -v /my/home/dir:/opt/git…. “ mitgegeben haben. Zum Zeitpunkt des Erstellens des Images wussten wir noch nicht, auf welches Verzeichnis /opt/git beim Start gemappt sein wird, deswegen können wir erst hier darauf reagieren: Wir erstellen einen user ‘gitserver’ und eine Gruppe ‘gitgroup’, die die herausgefundene UID bzw. GID haben.Da wir zu diesem Zeitpunkt root sind,dürfen wir das alles. Und nicht ganz zufällig weisen wir den lighttpd Server in seiner lighttpd.conf an, unter diesem Usernamen und dieser Gruppe zu laufen.

Der Effekt dieses kleine Hacks: lighttpd darf auf das eigentlich ausserhalb des Containers liegende Verzeichnis mit den git-Repositories frei zugreifen, weil der Linux server “meint”, es gehöre ihm.

Dann macht das Script noch pseudo-Userangaben, um git glücklich zu machen und erstellt einen user namens git mit Schreibrechten. Und in der letzten Zeile wird der lighttpd Server gestartet. Er wird nicht als daemon, sondern im Vordergrund gestartet, weil der Docker-Container sich sonst umgehend wieder beenden würde.

2.6.2015 - Durandal

Nachdem ich viel Zeit damit verbracht habe, KnockoutJS, Bootstrap, RequireJS und Konsorten zu lernen, und ein SPA Framework zu entwickeln, da entdecke ich, dass es bereits eines gibt: Durandal. Aufgebaut aus Bootstrap, RequireJS und KnockoutJS und mit vielen Goodies, die man sonst erst mühsam entwickeln müsste.

Jetzt bin ich dabei, Webelexis auf Durandal umzuschreiben. Naja, die steile Lernkurve war nicht ganz umsonst: So verstehe ich besser, was bei Durandal eigentlich geschieht.