Aus Linux-Magazin 04/2014

Webanwendungen in Clojure schreiben

© Galina Peshkova, 123RF.com

Clojure sieht aus wie Lisp und läuft überall, wo Java installiert ist. Dank praktischer Tools und ausgereifter Bibliotheken ist mit der Sprache auch rasch eine moderne Webanwendung programmiert.

Im Jahr 2007 tauchte Clojure [1] in der Softwarelandschaft auf. Die funktionale Programmiersprache vereint Lisp-artige Syntax mit einer Implementierung auf der Java Virtual Machine (JVM). Binnen weniger Jahre hat sich eine Clojure-Community mit Usergroups [2] und Konferenzen wie der Euro Clojure [3] gebildet.

Für die unter der Eclipse Public License stehende Sprache ist ein umfangreiches Ökosystem von Bibliotheken entstanden. Zudem profitiert sie von ihrer Java-Basis, dank der sie sich die VM mit Java und JRuby teilen kann. Auf Multiprozessorsystemen glänzt sie durch Nebenläufigkeit [4]. Für Java steht auch das “j” im Namen, der sich sonst an das Programmierkonzept Closure anlehnt.

Clojure-Verfechter loben die überdurchschnittliche Produktivität, die die ausdrucksstarke Sprache erlaubt, und beziehen sich gerne auf einen Aufsatz des Lisp-Verfechters und Unternehmers Paul Graham [5]. Das alles hat mittlerweile einige Internet-Startups dazu bewogen, auf die Sprache zu setzen, sie verwenden sie in großen Webanwendungen. Dieser Artikel zeigt, wie man Clojure nutzt, um einen einfachen Chat für den Webbrowser umzusetzen (Abbildung 1).

Abbildung 1: Diese Chat-Anwendung lässt sich mit Clojure und Javascript umsetzen.

Abbildung 1: Diese Chat-Anwendung lässt sich mit Clojure und Javascript umsetzen.

Bauen mit Leiningen

Die meisten Clojure-Nutzer setzen das Buildsystem Leiningen [6] ein, das sich auch um das Einspielen benötigter Bibliotheken kümmert. Wer es nicht in seinem Paketmanager findet, installiert es gemäß der Readme-Datei aus dem Quelltext. Das Kommando »lein -v« gibt die Leiningen-Version aus, beim ersten Aufruf lädt das Buildsystem noch ein paar Bibliotheken herunter und arbeitet eine Weile. Für diesen Artikel kam Version 2.3.4 zum Einsatz, daneben die jüngste stabile Clojure-Release 1.5.1 sowie Open JDK 7 als Java-Implementierung.

Das Projektverzeichnis für den Webchat legt das Kommando »lein new webapp« an und füllt es gleich mit einem Gerüst für die Anwendung. Abbildung 2 zeigt den Inhalt von »webapp« : Die Datei »project.clj« enthält Informationen über das Projekt und die Komponenten, die es benötigt – derzeit nur die aktuelle Clojure-Release.

Abbildung 2: Das Buildsystem Leiningen hat das Gerüst der Anwendung erzeugt.

Abbildung 2: Das Buildsystem Leiningen hat das Gerüst der Anwendung erzeugt.

Das Verzeichnis »src/« enthält den ersten Namespace der Anwendung »webapp.core« . Namespaces sind das wichtigste Mittel des Clojure-Programmierers, um Module mit unterschiedlichen Zuständigkeiten voneinander zu trennen. Dabei darf jeder Namespace alle oder einzelne Objekte in anderen referenzieren. Allerdings kann ein Namespace die Sichtbarkeit von Objekten nach außen durch »private« unterbinden.

Interaktiv: REPL

Die Datei »src/webapp/core.clj« enthält bereits eine Funktion. Sie lässt sich am interaktiven Kommandoprompt aufrufen, mit dem ein Clojure-Entwickler typischerweise neben dem Editor arbeitet. Wie in der Lisp-Familie üblich heißt die Eingabeaufforderung REPL (Read Eval Print Loop). Auf dem Terminal wechselt der Entwickler mit »cd« ins Projektverzeichnis und ruft dort »lein repl« auf.

Die folgende REPL-Sitzung nutzt die Funktion »require« , um den Code aus »src/webapp/core.clj« zu laden, und »in-ns« , um in den Namespace zu wechseln. Schließlich ruft sie die Funktion »foo« mit dem Argument »”It works!”« auf:

user=> (require 'webapp.core)
nil
user=> (in-ns 'webapp.core)
#<Namespace webapp.core>
webapp.core=> (foo "It works!")
It works! Hello, World!
nil

Eine einfache Änderung an der Quelltextdatei demonstriert, wie die REPL auf geänderten Code reagiert. In Listing 1 ist die Reihenfolge der Argumente in der »println« -Funktion vertauscht. Nach dem Abspeichern im Editor lädt man in derselben REPL nur den Namespace neu, um die geänderte Ausgabe zu erhalten:

Listing 1

src/webapp/core.clj

01 (defn foo
02   "I don't do a whole lot."
03   [x]
04   (println "Hello, World!" x))
webapp.core=> (require 'webapp.core :reload)
nil
webapp.core=> (foo "It works!")
Hello, World! It works!
nil

Diese Fähigkeit der REPL ermöglicht es dem Entwickler, fortlaufend seine Änderungen am Code auszuprobieren. Viele Editoren wie Vim oder Emacs können eine REPL integrieren.

Gebündelte Bibliotheken

Die Clojure-Welt kennt kaum große, monolithische Frameworks. Die meisten Anwendungen verwenden eine Sammlung kleiner Bibliotheken, von denen jede einem klar eingegrenzten Zweck dient. Typische Clojure-Webanwendungen setzen auf die Bibliothek Ring, die eine Abstraktion des HTTP-Protokolls darstellt. Die Beispielanwendung wird allerdings nicht direkt Ring benutzen, sondern Compojure [7], das auf Ring aufsetzt. Dazu kommen einige weitere Libraries, die die Datei »project.clj« als Abhängigkeiten definiert (Listing 2).

Listing 2

project.clj

01 :dependencies [[org.clojure/clojure "1.5.1"]
02                [compojure "1.1.6"]
03                [hiccup "1.0.4"]
04                [enlive "1.1.5"]
05                [http-kit "2.1.16"]]

Das Hinzufügen einer neuen Bibliothek ist eine der wenigen Gelegenheiten, bei denen der Entwickler seine REPL neu startet. Dafür gibt es zwar auch Tools, doch ein einfaches [Strg]+[D] und die erneute Eingabe von »lein repl« tun es ebenso. Anschließend lädt Leiningen die erforderlichen Dateien automatisch herunter. Nun folgen weitere Änderungen an der Datei »core.clj« im Editor. Diese Zeilen holen Code aus der Compojure-Bibliothek in den aktuellen Namespace:

(ns webapp.core
  (:require [compojure.core :refer [defroutes GET]]))

Dies importiert »defroutes« und »GET« aus dem Namespace »compojure.core« und macht sie nutzbar für eine erste einfache Anwendung:

(defroutes app
  (GET "/" [] "<h1>Hello World</h1>"))

Hier definiert »app« einen Ring-Handler. Er begrüßt Websurfer, die per HTTP-GET die Adresse »/« abrufen, mit “Hello World”. Damit das funktioniert, benötigt die Anwendung aber einen HTTP-Server. Den liefert Httpkit [8], das zu dem erforderlichen Namespace in »core.clj« hinzukommt:

(ns webapp.core
  (:require [compojure.core :refer[defroutes GET]]
            [org.httpkit.server :as http]))

Nun stehen alle Funktionen aus »org.httpkit.server« unter dem Präfix »http/« zur Verfügung. Damit lässt sich der Webserver in der REPL ausprobieren:

user=> (require 'webapp.core :reload)
nil
user=> (ns webapp.core)
nil
webapp.core=> (def srv (http/run-server app{:port 8080}))
#'webapp.core/srv

Die Funktion »http/run-server« startet die Anwendung, »def« bindet den Rückgabewert an »srv« . Im Browser ist die Anwendung nun unter »http://localhost:8080« erreichbar. Die Funktion zum Starten des Webservers hat wiederum eine Funktion zurückgegeben. Das lässt sich in der REPL mit dem Prädikat »fn?« überprüfen. Ruft man die Funktion selbst auf, fährt der Server herunter:

webapp.core=> (fn? srv)
true
webapp.core=> (srv)
nil

Der Server lässt sich mit »(http/run-server app {:port 8080}))« wieder starten, ohne einen Neustart der REPL. Man kann sogar mehrere Instanzen an unterschiedlichen Ports betreiben.

Templating

Die Datei »project.clj« nennt mit Hiccup [9] und Enlive [10] noch zwei weitere Abhängigkeiten. Beide sind Templating-Bibliotheken, jede verfolgt aber einen anderen Ansatz.

Listing 3 macht Hiccup verfügbar und »app« hat sich geändert: Die Anwendung verwendet nicht mehr die Zeichenkette “Hello, World!”, sondern ruft die Funktion »root« auf, die in den Zeilen darüber definiert ist. Die verwendet Hiccup, um HTML auszugeben. Eines der interessantesten Features von Clojure besteht darin, dass der Entwickler Teile des Codes ändern kann, ohne den Rest der virtuellen Maschine anzutasten. Da das Erzeugen des HTML-Codes in eine eigene Funktion ausgelagert ist, darf der Entwickler sie verändern und sieht sofort die Auswirkung auf die Anwendung.

Listing 3

src/webapp/core.clj

01 (ns webapp.core
02   (:require [compojure.core :refer [defroutes GET]]
03             [org.httpkit.server :as http]
04             [hiccup.core :as hiccup]))
05
06 (defn root
07   []
08   (hiccup/html [:h1 "Hello, Hiccup!"]))
09
10 (defroutes app
11   (GET "/" [] (root)))

Guten Tag!

Beispielsweise könnte man den Gruß abändern, etwa in “Guten Tag”. Befindet sich die REPL bereits im Namespace »webapp.core« , stehen die definierten Aliase zur Verfügung:

webapp.core=> (hiccup/html [:h1 [:em "GutenTag"] ", Hiccup!"])
"<h1><em>Guten Tag,</em>, Hiccup!</h1>"

Der Rückgabewert sieht gut aus – folgender Code hält die Änderung in »src/webapp/core.clj« fest:

(defn root
  []
  (hiccup/html [:h1 [:em "Guten Tag"] ",Hiccup!"]))

Die nächste Eingabe lädt nach dem Speichern der Datei den Namespace neu:

webapp.core=> (require 'webapp.core :reload)
nil

Den Server braucht der Entwickler dabei nicht neu zu starten. Das neue Verhalten der Funktion »root« sollte schon nach dem Neuladen der Seite im Browser sichtbar sein.

Hiccup verwendet einen Clojure-typischen Ansatz, um HTML zu erzeugen, indem es die Daten in einfachen Strukturen abbildet. Da die gebräuchlichen Typen wie Listen, Vektoren, Maps und Sets eine gemeinsame Programmierschnittstelle besitzen, lassen sich Hiccup-Templates praktischerweise mit den üblichen Clojure-Mitteln verarbeiten.

Wer mit Webdesignern zusammenarbeitet, die den Umgang mit HTML gewohnt sind, hat mit Hiccup allerdings nicht viel Freude. Für dieses Szenario bietet sich besser die Templating-Bibliothek Enlive an. Ein simples Template, als Datei »resources/templates/index.html« gespeichert, zeigt Listing 4.

Listing 4

resources/templates/index.html

01 <!DOCTYPE html>
02 <html>
03 <head>
04   <meta http-equiv="Content-Type"
05         content="text/html; charset=UTF-8">
06 </head>
07 <body><h1></h1></body>
08 </html>

Die neue Version von »src/webapp/core.clj« in Listing 5 verwendet »:require« , um die Bibliothek Enlive einzubinden. Die Funktion »deftemplate« ersetzt die vorige Definition von »root« und wendet das Template an. Sie nimmt keine Argumente entgegen und setzt einfach »Hello, Enlive!« in das HTML-Element »<h1>« des Template ein. Um die Änderung sichtbar zu machen, reicht es, den Namespace in der REPL sowie die Seite im Browser neu zu laden.

Listing 5

src/webapp/core.clj

01 (ns webapp.core
02   (:require [compojure.core :refer [defroutes GET]]
03             [org.httpkit.server :as http]
04             [hiccup.core :as hiccup]
05             [net.cgrand.enlive-html :refer [deftemplate]
06                                   :as enlive]))
07
08 (deftemplate root "templates/index.html"
09   []
10   [:h1] (enlive/append "Hello, Enlive!"))
11
12 (defroutes app
13   (GET "/" [] (root)))

Wer will, kann diese Experimente auf der REPL auf eigene Faust fortsetzen, eine Kleinigkeit ändern, vielleicht etwas kaputt machen und wieder reparieren. Als nützlich erweist sich dabei, dass sich in der REPL Dokumentation und Quelltext zu Funktionen nachschlagen lassen. Dazu dienen die Kommandos »clojure.repl/doc« und »clojure.repl/source« , wie die Beispielsitzung in Listing 6 zeigt.

Listing 6

Dokumentation und Quelltext

01 webapp.core=> (clojure.repl/doc foo)
02 -------------------------
03 webapp.core/foo
04 ([x])
05   I don't do a whole lot.
06 nil
07 webapp.core=> (clojure.repl/source foo)
08 (defn foo
09   "I don't do a whole lot."
10   [x]
11   (println "Hello, World!" x))
12 nil

Eine Chat-Anwendung

Aus den ersten Gehversuchen mit Clojure soll nun ein Webchat werden, der Websockets verwendet. Die Beispielanwendung besteht aus nur vier Dateien. In »project.clj« (Listing 7) findet sich die bereits bekannte Liste der Abhängigkeiten: Clojure selbst und drei Bibliotheken. Daneben macht aber der Schlüssel »:main« das folgende »webapp.main« zum Haupt-Namespace der Anwendung.

Listing 7

project.clj

01 (defproject webapp "0.1.0-SNAPSHOT"
02   :main webapp.main
03   :profiles {:uberjar {:aot :all}}
04   :dependencies [[org.clojure/clojure "1.5.1"]
05                  [http-kit "2.1.16"]
06                  [compojure "1.1.6"]
07                  [enlive "1.1.5"]])

Die verschachtelte Map neben »:profiles« gibt an, dass Leiningen alle Namespaces vor dem Ausführen (»:aot« , “ahead of time”) zu Java-Class-Dateien kompilieren soll. Diese beiden Einstellungen machen es möglich, die Anwendung später als Jar-Archiv zu exportieren.

Der Namespace »webapp.core« , wie ihn Listing 8 zeigt, hat sich allerdings merklich verändert. Unter den erforderlichen Namespaces (»:require« ) findet sich nun auch die Funktion »wrap-params« aus »ring.middleware.params« . Mit ihrer Hilfe kann die Anwendung auf Parameter aus HTTP-Anfragen in Form eines Hash zugreifen.

Listing 8

src/webapp/core.clj

01 (ns webapp.core
02   (:require [compojure.core :refer [routes GET]]
03             [org.httpkit.server :as http]
04             [net.cgrand.enlive-html :as enlive]
05             [ring.middleware.params :refer [wrap-params]]))
06
07 (enlive/deftemplate root "templates/index.html"
08   [request]
09   [:#name] (enlive/set-attr :value (get-in request [:params "name"])))
10
11 (defn broadcast
12   [channels message]
13   (doseq [channel (deref channels)]
14     (http/send! channel message)))
15
16 (defn new-client
17   [channels channel name]
18   (swap! channels conj channel)
19   (doto channel
20     (http/on-close (fn [_]
21                      (swap! channels disj channel)
22                      (broadcast channels (str name " has left"))))
23     (http/on-receive (fn [msg] (broadcast channels (str name ": " msg)))))
24   (broadcast channels (str name " joined")))
25
26 (defn websocket
27   [channels]
28   (fn [request]
29     (let [name (get-in request [:params "name"])]
30       (http/with-channel request channel
31         (if (http/websocket? channel)
32           (new-client channels channel name)
33           (http/send! channel {:status  426
34                                :headers {"Content-Type" "text/plain"}
35                                :body    "You need WebSockets!"}))))))
36
37 (def app
38   (let [channels (atom #{})]
39     (wrap-params
40       (routes
41         (GET "/"   [] root)
42         (GET "/ws" [] (websocket channels))))))

Auch das »root« -Template in Zeile 7 sieht anders aus. Es nimmt nun »request« als einziges Argument entgegen. Wird das Template aufgerufen, sucht es im HTML-Objektbaum nach dem CSS-Selektor »#name« . Wird es fündig, setzt es den passenden Wert aus dem Hash »request« ein, der folgende Struktur besitzt:

{:params {"name" "gesuchter Wert"}}

Ab Zeile 27 definiert das Listing die Funktion »app« . Diese legt zunächst ein Atom an, das ein leeres Set enthält und an das Symbol »channels« gebunden ist. Dieses Atom wird Verweise auf alle Verbindungen zum Chatroom speichern. Bei einem Atom handelt es sich in Clojure um einen Container, der andere Objekte aufnimmt. Sie lassen sich dann nur mittels atomarer Transaktionen verändern. Das heißt, eine Änderung gelingt entweder vollständig oder Clojure versucht es noch einmal, sie durchzuführen.

Veränderlich

Das Atom vollbringt noch ein weiteres Kunststück: Mit seiner Hilfe lassen sich die in funktionalen Sprachen eigentlich unveränderbaren Datenstrukturen in Clojure dennoch ändern. Normalerweise geben Funktion nämlich nur aktualisierte Kopien einer Datenstruktur zurück, etwa beim Vektor »v« in der folgenden interaktiven Sitzung. Die Funktion »conj« fügt ihm ein zusätzliches Element hinzu – eigentlich nur der ausgegebenen Kopie – und der ursprüngliche Vektor bleibt unverändert:

webapp.core=> (def v [1 2])
#'webapp.core/v
webapp.core=> v
[1 2]
webapp.core=> (conj v 3)
[1 2 3]
webapp.core=> v
[1 2]

Schließt der Clojure-Programmierer aber einen Vektor oder ein Set in ein Atom ein, kann er es als veränderliche Datensammlung benutzen, dazu mit garantiert exklusivem Zugriff:

webapp.core=> (def a (atom [1 2]))
#'webapp.core/a
webapp.core=> a
#<Atom@320bdadc: [1 2]>
webapp.core=> (swap! a conj 3)
[1 2 3]
webapp.core=> a
#<Atom@320bdadc: [1 2 3]>

Der Code innerhalb des »let« -Ausdrucks (Listing 8, Zeilen 38 bis 42) ruft die Funktion »wrap-params« mit dem Rückgabewert eines »routes« -Aufrufs auf. Innerhalb von »routes« sind zwei Routen zu sehen. Die erste ist übrigens kein Aufruf der Funktion »root« , denn der hieße in Clojure »(root)« . Sie wird stattdessen als Wert übergeben. Um das Aufrufen kümmert sich Ring und übergibt dabei den HTTP-Request als Argument.

Neu ist die zweite Route, die den Pfad »”/ws”« bedient. Sie ist als Rückgabewert der Funktion »websocket« mit dem Argument »channels« definiert. Wie im Funktionskörper (Zeilen 26 bis 35) zu sehen ist, gibt sie eine Funktion zurück, die als Argument einen eingehenden HTTP-Request entgegennimmt. Aus dessen Parameterliste extrahiert sie »name« . Im nächsten Schritt bindet »http/with-channel« das Symbol »channel« an einen asynchronen Kanal, der eine Websocket-Verbindung sein sollte. Ist er es nicht, meldet die Anwendung einen Fehler und setzt den HTTP-Status »426 Upgrade Required« (Abbildung 3).

Abbildung 3: Der Pfad »/ws« ist für Websockets reserviert, andernfalls beschwert sich die Chat-Anwendung.

Abbildung 3: Der Pfad »/ws« ist für Websockets reserviert, andernfalls beschwert sich die Chat-Anwendung.

Im Erfolgsfall aber gibt der Code den Kanal an die Funktion »new-client« weiter, zusammen mit dem Parameter »name« und dem Atom »channels« . Wie der Name andeutet, ist »new-client« (Zeilen 16 bis 24) für neu aufgenommene Verbindungen zuständig. Die Funktion ruft zunächst »swap!« mit dem Atom »channels« als Argument auf. »swap!« wendet die restlichen Argumente auf die Werte im Atom an und ersetzt dessen Inhalt mit dem Ergebnis.

Der neue Wert im Atom ist »(conj previous-value channel)« , also das Set der existierenden Verbindungen zuzüglich der neuen. Darauf folgt der »doto« -Block, der zwei Handler für den Kanal definiert. Beim Schließen des Kanals (»http/on-close« ) kommt wieder »swap!« zum Einsatz, aber diesmal mit »disj« , das im Gegensatz zu »conj« ein Element aus dem Set »channels« entfernt. Dann sendet die Anwendung einen Broadcast an die restlichen Kanäle, dass ein Gesprächsteilnehmer ausgestiegen ist.

Durchsage an alle

Beim Empfang (»http/on-receive« ) einer neuen Nachricht schickt das Programm sie einfach an alle Clients. Im letzten Schritt meldet »new-client« die Ankunft eines neuen Teilnehmers.

Die »broadcast« -Funktion selbst ist ebenfalls einen Blick wert (Listing 8, Zeilen 11 bis 14). Sie nimmt zwei Argumente entgegen, das Atom mit allen Kanälen und eine Zeichenkette, die als Nachricht verschickt werden soll. Mit »deref« extrahiert sie aus dem »channels« -Atom das Set mit den Kanälen. Dann iteriert sie über alle Kanäle und verwendet die Funktion »http/send!« , um die Nachricht an jeden einzelnen zu übermitteln.

Wer langsam unruhig wird und die Anwendung starten möchte, sollte sich noch kurz gedulden und die restlichen zwei Dateien ansehen: »src/webapp/main.clj« (Listing 9) enthält einen kleinen Namespace, der beim Starten der Anwendung hilft. Er bindet den HTTP-Server sowie die »app« aus »webapp.core« ein.

Listing 9

src/webapp/main.clj

01 (ns webapp.main
02   (:require [webapp.core :refer [app]]
03             [org.httpkit.server :as http])
04   (:gen-class))
05
06 (defn -main []
07   (http/run-server app {:port 8080})
08   (println "The server is up!"))

Die Funktion »-main« entspricht der »main« -Methode aus der Java-Welt – sie tritt in Aktion, wenn man die Anwendung auf der Kommandozeile aufruft. Sie startet den Server und gibt eine kurze Statusmeldung aus. Das Keyword »:gen-class« im Namespace veranlasst den Compiler, »webapp.main« zu einer Java-Klasse zu kompilieren.

Listing 10 schließlich ist das Enlive-Template. Es enthält HTML sowie etwas Javascript, um die Websocket-Verbindungen im Browser zu öffnen und HTML-Sonderzeichen zu maskieren.

Listing 10

resources/templates/index.html

01 <!DOCTYPE html>
02 <html>
03 <head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></head>
04 <body>
05 <h1>Chat</h1>
06 <section id="log"></section>
07 <input type="text" id="input" autofocus/>
08 <input type="hidden" id="name"/>
09 <input type="button" id="button" value="Send"/>
10 <script language="javascript" type="text/javascript">
11 function init() {
12   var log = document.getElementById("log");
13   var input = document.getElementById("input");
14   var name = document.getElementById("name").value;
15   var set_callback = function (fn) {
16     document.getElementById("button").addEventListener("click", fn, false);
17     input.addEventListener("keydown", function (event) {
18         if (event.keyCode == 13) { fn(); }
19     });
20   }
21   if (name) {
22     var host = document.location.host;
23     var websocket = new WebSocket("ws://" + host + "/ws?name=" + name);
24     websocket.onmessage = function (event) {
25       var pre = document.createElement("pre");
26       pre.innerHTML = event.data
27         .replace(/&/g,'&amp;')
28         .replace(/</g,'&lt;')
29         .replace(/>/g,'&gt;');
30       log.appendChild(pre);
31       window.scrollTo(0, document.body.scrollHeight);
32     };
33     set_callback(function () {
34         websocket.send(input.value);
35         input.value = "";
36     });
37   } else {
38     log.innerHTML = "<p>What's your name?</p>";
39     set_callback(function () {
40         window.location = "?name=" + input.value;
41     });
42   }
43 }
44 window.addEventListener("load", init, false);
45 </script>
46 </body>
47 </html>

Startet der Entwickler die Anwendung nun mit »lein run« , ruft dies die »-main« -Funktion im Namespace »main« auf. Statt den Server aus dem Projektverzeichnis heraus aufzurufen, kann man die ganze Anwendung auch in ein Jar-Archiv packen, das sich fürs Deployment oder die Weitergabe an Kunden eignet. Es enthält alles, was der Webchat benötigt. Dafür gibt es das Leiningen-Kommando »lein uberjar« . Das resultierende Jar-Archiv erfordert nur eine Java-Runtime und lässt sich per »java -jar Archivname.jar« in Betrieb nehmen (Abbildung 4).

Abbildung 4: Leiningen packt die komplette Anwendung samt Bibliotheken in ein lauffähiges Jar-Archiv.

Abbildung 4: Leiningen packt die komplette Anwendung samt Bibliotheken in ein lauffähiges Jar-Archiv.

Ausblick

Hat man sich an die Clojure-Syntax und das Arbeiten auf der REPL gewöhnt, lassen sich mit Leiningen und einer Handvoll Bibliotheken rasch Webanwendungen umsetzen. Für den Webentwickler sind dabei Templating-Systeme wie Hiccup [9] und Enlive [10] von Bedeutung, sie stellen aber nur einen Bruchteil des Clojure-Ökosystems dar. Wer mehr über Clojure wissen möchte, findet eine Reihe von Büchern über die Programmiersprache auf dem Markt [11], vornehmlich allerdings in englischer Sprache. Wer Übung braucht, findet auf der Website 4clojure.com [12] eine große Sammlung von Programmieraufgaben. Der Blog-Aggregator Planet Clojure [13] schließlich trägt zusammen, was es in der Clojure-Community Neues gibt. (mhu)

Der Autor

Jan Stepien arbeitet als Entwickler bei Stylefruits.de und betätigt sich als Coach bei Clojureworkshop.de. Er hat an der Technischen Universität Warschau studiert, begeistert sich für Clojure und Haskell und geht gerne zum Bergsteigen.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 6 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben