Einstieg in die Entwicklung von Web-Apps mit Meteor
Seite 2: Die erste App
Im myapp-Ordner werden je eine HTML-, CSS- und eine JS-Datei angelegt, die, bis auf die CSS-Datei, bereits erste Code-Fragmente enthalten. Wechselt man in den myapp-Ordner, lässt sich dort die Anwendung mit
$ meteor run
starten (der Befehl run ist der Default-Befehl und kann entfallen). Die eigene erste Meteor-Anwendung lässt sich im Browser nach Eingabe von http://localhost:3000 anschauen.
Ein Blick in die generierte JavaScript-Datei zeigt, dass Funktionen für Client und Server enthalten sind:
if (Meteor.isClient) {
someFunction = function() {
// your code goes here
};
...
}
if (Meteor.isServer) {
Meteor.startup(function() {
// code to run on server at startup
});
...
}
Bei kleinen Anwendungen ist dies noch überschaubar, allerdings schwindet die Übersichtlichkeit mit wachsender Codelänge, weshalb ein derartiger Aufbau nicht von Vorteil ist. Nachteilig ist außerdem, dass serverseitiger Code an den Client ausgeliefert wird, was eigentlich vermieden werden sollte.
Um eine bessere Trennung des Quellcodes zu gewährleisten, lassen sich die Dateien nach folgenden Konventionen aufteilen: Alle Dateien im Ordner client werden an den Client ausgeliefert, alle im Ordner server verbleiben auf dem Server. Dateien in lib-Ordnern werden in ihrer jeweiligen Umgebung immer zuerst geladen (hier lassen sich zum Beispiel andere JavaScript-Bibliotheken ablegen und von dort laden). Dateien, die dem Muster "main*.js" entsprechen, werden zuletzt geladen, alle anderen in alphabetischer Reihenfolge. Für statische Inhalte wie Bilder gibt es den public-Ordner, dessen Inhalt Meteor beim Deployment nicht komprimiert.
Im Folgenden sind einige Funktionen und Eigenschaften von Meteor an einer Demo-Applikation namens "Poker-Quiz" erklärt. Ihr Quellcode ist auf GitHub zu finden. Allein spielen ist nicht besonders spannend, erst zusammen mit mehreren Spielern lassen sich das automatisch ändernde Ranking und das Aktivitäten-Log in Aktion beobachten.
MongoDB und Collections
Meteor nutzt MongoDB für die Persistenz, wobei der Zugriff auf die serverseitigen Daten transparent erfolgen soll. Hierfür bietet die Datenbank-API auf dem Client vergleichbare Befehle wie die auf dem Server. Das Meteor-Team hat die Implementierung "Minimongo" getauft. Der Abgleich der Daten zwischen Client (Minimongo) und Server (MongoDB) ist für den Entwickler versteckt.
Für MongoDB üblich, werden Daten in Kollektionen abgebildet, deren Definition vergleichsweise einfach ist. Im Beispiel sind solche für den Punktestand der Spieler und ihre Statusmeldungen (Logs) in der Datei common/Collections.js definiert. Damit stehen die beiden Kollektionen sowohl auf dem Client als auch auf dem Server zur Verfügung, und Meteor synchronisiert automatisch. Die Quizfragen existieren in der Questions-Kollektion hingegen nur auf dem Server.
Jede Kollektion erhält einen eindeutigen Namen als Argument und ist damit automatisch persistent:
Players = new Meteor.Collection("players");
Logs = new Meteor.Collection("logs");
Soll nicht persistiert werden, übergibt man keinen Namen. Dieses Vorgehen eignet sich gut, um Daten temporär einem Template zur Verfügung zu stellen.
Um mit den Kollektionen zu arbeiten, lehnt sich Meteor eng an die existierende MongoDB-API an und kapselt wichtige Datenbankfunktionen wie find() beziehungsweise findOne(), insert(), update() und remove(). Beispiele für den Einsatz sind in der Dokumentation von Meteor aufgeführt.
Standardmäßig ist eine neue Meteor-Applikation mit dem "insecure"-Flag versehen, was es erlaubt, jeden Client beziehungsweise jeden Nutzer (auch anonyme, nicht authentifizierte und nicht autorisierte) auf die Datenbank zugreifen und jede beliebige Operation ausführen zu lassen. Für Entwicklungszwecke ist das zunächst sinnvoll und erleichtert die Arbeit. Im produktiven Einsatz ist es allerdings sinnvoller, das Flag zu deaktivieren und die Zugriffe auf die Collections/MongoDB durch sogenannte Allow-and-Deny-Mechanismen abzusichern.
Die Allow-Konfiguration beschreibt, wer welche Operation auf welcher Kollektion ausführen darf, die Deny-Konfiguration entsprechend, wem etwas nicht erlaubt ist.
Messages.allow({
insert: function (userId, msg) {
// only logged-in users can insert a new message that they own
return (userId && msg.owner == userId);
},
fetch: ['owner']
});
Messages.deny({
remove: function (userId, msg) {
//can't remove locked messages
return msg.locked;
},
fetch: ['locked']
});