Clojure Tutorial, Teil 3: MongoDB


Dies ist der dritte Artikel in einer kleinen Serie, die meine ersten Schritte in der neuen funktionalen Programmiersprache Clojure dokumentieren soll. Die Artikel werden in unregelmäßigen Abständen hier publiziert.

Heute hab ich mit meinen Gehversuchen in Clojure weiter gemacht. Diesmal ging’s darum, die NoSQL-DB MongoDB anzusprechen. Ich will mir nicht dauernd SQL-Datenbanken wie PostgreSQL oder MySQL ans Bein binden, nur um mich anschliessend mit den elenden JOIN-Statements rumzuschlagen. Meine SQL-Kenntnisse hören so ungefähr bei SELECT * FROM X auf - und so soll das auch bleiben.

Deshalb stand heute ein simples Anwendungsgerüst auf meinem Stundenplan, das eine Webseite zur Verfügung stellt, die Daten aus einer MongoDB liefert. Also, ran ans Werk.

Wer möchte, kann sich das fertige Projekt von Github holen.

Abhängigkeiten von Leiningen verwalten lassen

Damit ich mich nicht ewig in der Dependency Hell herumirre, hab ich mein Projekt mit leiningen aufgesetzt. Das ist ein kleines Tool zur Paketverwaltung, wie man’s von Perl & Co kennt. Als erstes braucht man leiningen selbst. Dazu lädt man dort das Skript herunter, macht es ausführbar und schon installiert sich die Anwendung. Den Pfad zu leiningen legt man am besten im PATH ab.

Anschliessend kann man mit folgender Kommandozeile das neue Projekt anlegen lassen:

lein new PROJEKTNAME

Jetzt werden einige Dateien und Verzeichnisse angelegt:

  • lib - Ordner für Abhängigkeiten
  • src - Ordner für Sourcecode
  • classes - Ordner für die Kompilate
  • project.clj - Projekteinstellungen, enthält auch die Abhängigkeiten

Daneben wird auch gleich eine .gitignore angelegt. Sehr praktisch, damit man nicht plötzlich die ganzen Klassen und Bibliotheken eincheckt.

Jetzt müssen noch die Abhängigkeiten in der Datei project.clj eingetragen werden. Da ich eine Webanwendung bauen will, gehören dazu u.A. compojure, ring und der MongoDB-Adapter congomongo. Hier ist der relevante Teil meiner Projektdatei:

(defproject tweetit "0.0.1-SNAPSHOT"
    :description "FIXME: no description"
    :dependencies [[org.clojure/clojure "1.2.0"]
            [org.clojure/clojure-contrib "1.2.0"]
            [compojure "0.5.2"]
            [ring/ring-core "0.3.5"]
            [ring/ring-devel "0.3.5"]
            [ring/ring-jetty-adapter "0.3.5"]
            [ring/ring-servlet "0.3.5"]
            [congomongo "0.1.3-SNAPSHOT"]]
  :main tweetit.clj)

Jetzt kann leiningen die Abhängigkeiten installieren:

lein deps

Webapplikationen mit Compojure

Als erstes lege ich die Datei mongo.clj an, die später mit dem Befehl (:use mongo) eingebunden wird. Dort will ich alles sammeln, was mit dem Zugriff auf die MongoDB zu tun hat. Hier ist der Inhalt:

(ns mongo
    (:use somnium.congomongo))

(defn get-all-notes []
    (let [notes (fetch :notes :only [:title :content])]
        (apply
            str
                (for [note notes]
                    (str "<strong>" (:title note) "</strong>" (:content note) "
                ")
            )
        )
    )
)

(defn add-note []
    (insert! :notes
        {:title "title #1", :content "this is a first note"})
)

Hier sind zwei Funktionen definiert. Die Funktion get-all-notes liest Dokumente aus der Datenbank notes, die Funktion add-note fügt ein Dokument hinzu. Das ist immer das gleiche Dokument, da ich diese Funktion nur für den ersten Test brauche.

Jetzt muss noch die eigentliche Webanwendung angelegt werden. Dazu lege ich die Datei src/tweetit.clj mit folgendem Inhalt an:

(ns tweetit
  (:use compojure.core
	ring.adapter.jetty)
  (:use ring.middleware.reload)
  (:use ring.middleware.stacktrace)
  (:use somnium.congomongo)
  (:use mongo)) ; my own file

;; set up congomongo + mongodb
(mongo!
  :db "notes", :host "127.0.0.1")

(defroutes test-routes
  (GET "/" []
    (str "<h1>Testing</h1>" (add-note) "<br />" (get-all-notes) "<br />"))
  (ANY "*" []
    {:status 404, :body "Page not found."})
)

(def app
  (-> (var test-routes)
      (wrap-reload '(tweetit))
      (wrap-reload '(nlp))
      (wrap-reload '(mongo))
      (wrap-stacktrace))
)

(defn dev []
  (run-jetty #'app {:port 8080})
)

Jetzt nochmal aufgepasst: In der Routendefinition test-routes wird bei einem GET-Request auf / ein String zusammengebaut, der meine Testfunktion add-note aufruft, und anschliessend noch get-all-notes aufruft.

(str "<h1>Testing</h1>" (add-note) "<br />" (get-all-notes) "<br />"))

Es wird also bei jedem Aufruf der Seite das gleiche Dokument in die Datenbank geschrieben. Das muss natürlich noch geändert werden, so dass man per Formular das Dokument verändern kann. Aber für heute reicht das erstmal.

Start der MongoDB

Bevor die Anwendung gestartet wird, muss natürlich die MongoDB hochgefahren werden. Weil die zwar den Pfad data/db erwartet, ihn aber nicht selbst anlegt, mache ich das schnell selbst. Anschliessend starte ich die MongoDB und gebe den Pfad zum Datenverzeichnis an:

MONGO_HOME/bin/mongod --dbpath /pfad/zum/parent/verzeichnis # hier muss data/db nicht angegeben werden
Thu Jan 13 20:50:17 MongoDB starting : pid=2661 port=27017 dbpath=. 64-bit
Thu Jan 13 20:50:17 db version v1.6.5, pdfile version 4.5
Thu Jan 13 20:50:17 git version: 0eb017e9b2828155a67c5612183337b89e12e291
Thu Jan 13 20:50:17 sys info: Linux domU-12-31-39-06-79-A1 2.6.21.7-2.ec2.v1.2.fc8xen #1 SMP Fri Nov 20 17:48:28 EST 2009 x86_64 BOOST_LIB_VERSION=1_41
Thu Jan 13 20:50:17 [initandlisten] waiting for connections on port 27017
Thu Jan 13 20:50:17 [websvr] web admin interface listening on port 28017

Etwas seltsam, aber vielleicht erschliesst sich mir der Grund noch.

Start der Webanwendung

Meine Anwendung starte ich jetzt wieder per leiningen. Dazu rufe ich eine REPL auf, lade meinen Sourcecode und rufe anschliessend die Funktion dev auf, die den Jetty-Server hochfährt. Äderungen an meinem Sourcecode werden automatisch geladen und so kann ich das Ergebnis direkt nach dem Neuladen der Seite begutachten.

$ lein repl
REPL started; server listening on localhost:30939.
=>

Anschliessend in der REPL:

=$> (use 'tweetit)
nil
=$> (dev)
2011-01-13 20:49:12.755:INFO::Logging to STDERR via org.mortbay.log.StdErrLog
2011-01-13 20:49:12.757:INFO::jetty-6.1.26
2011-01-13 20:49:12.792:INFO::Started SocketConnector@0.0.0.0:8080</pre>

Jetzt kann ich unter http://127.0.0.1:8080 auf meine Seite zugreifen. DIe Ausgabe sollte etwas in dieser Art sein:

Testing
{:_id #, :content "this is a first note", :title "title #1"}
title #1this is a first note

Weitere Artikel

Der letzte PR

Fahrrad geklaut

Erstes Review des Sony XZ Premium

Unterschiedliche Sichtweisen

Vorbestellt: Sony Xperia XZ Premium

Giants Run 2017

Gelesen: SciFi Serie 'The Expanse'

Neue Tastatur: Pok3r Vortex RGB

New minecraft survival mod for 1.11.2

Änderungen beim Flug mit Quadkoptern