dWing — die Welt ist nicht gerecht

sign in

Web-Frameworks: ActiveRecord, Lazy-Loading

Ein ActiveRecord, oder besser gesagt Object Relation Mapper (ORM) bildet das M aus MVC. Also der Punkt bei dem es um die Speicherung der Daten geht.

Das ActiveRecord Entwurfsmuster gibt dabei einige Basisoperationen vor. Meist einen Konstruktor mit dem ein neues Objekt erstellt wird, eine save Methode um Änderungen in die Datenbank zu speichern, bei einem noch nicht in der Datenbank vorhandenem Objekt wird dies hineingeschrieben. Eine remove Methode löscht das Objekt. Statische Methoden dienen dazu Objekte aus der Datenbank zu lesen.


Ein ActiveRecord selbst zu implementieren ist eigentlich einfacher als man denkt. Zumindest ein einfaches ActiveRecord was keine hasOne, hasAndBelongsToMany Beziehungen hat. Mein dWing hat ein ganz einfaches ActiveRecord. Benutzten kann man es wie folgt:

class Comment extends ActiveItem
{
  protected $tableName = 'comments';
  protected $definition = array('text' => 'html', 'user_id' => 'user',
    'time' => 'time', 'content_id' => 'required', 'content_type' => 'required');
}

Mein allgemeiner __get()ter sorgt dabei für dynamische hasOne und hasMany Beziehungen, ganz ohne das man diese deklarieren muss.

Dabei wird alles über Lazy-Loading realisiert. Es werden die Objekte nur dann erzeugt und aus der Datenbank geholt wenn diese benötigt werden.

Greife ich z.b. auf $news->comments zu, wird zuerst ein CommentIterator erzeugt. Dieser implementiert auch das Countable Interface. Das bedeutet, das wenn ich ein count($news->comments) mache wird lediglich ein SQL COUNT() ausgeführt. Erst wenn ich den Iterator anfange zu iterieren wird das vollständige SELECT gemacht. Dabei liebe ich auch PHPs PDO.

$statement->setFetchMode(PDO::FETCH_CLASS, 'Comment');
$this->elements = $statement->fetchAll(); 

Hier erzeugt mir PDO direkt meine Klassen ohne das ich dies selbst tun müsste.


Nun zu CakePHP. Was soll ich sagen? Obwohl die Modelle eine save() Methode haben und die besagten hasOne usw. Beziehungen würde ich das Cake ORM keinesfalls als ActiveRecord bezeichnen.

Einerseits sind die Models anscheinend nicht als Globale Klasse verfügbar. Im Controller definiert man über eine $uses Variable welche Modelle man benutzen will. Dann sind diese als $this->Model verfügbar. Und wenn man über eine der find() Methoden Daten haben will werden die nicht als Objekt oder Iterator über Objekte zurückgegeben sondern als ein wild gemischtes Array. Die Cake Hilfe gibt ein Beispiel darüber. Ich muss ehrlich sagen, etwas unintuitiveres habe ich noch nicht gesehen.

Auf der Unteren Ebene kriege ich ein Array, das ist auch das einzig Intuitive. Darunter ein Assoziatives Array, auch bekannt als Hashmap, Dictionary oder einfach Objekt. Dieses hat als Indizes die Modellnamen. Will ich eine News haben ist diese über $news['News'] verfügbar. Redundanz ist ja was schönes oder nicht? Und die hasOne, hasMany usw Beziehungen sind ebenfalls in der Ebene.

Die Kommentare sind also in $news['Comment'][0] usw. An sich ist kein großer Unterschied zwischen $news->comments und $news['Comment']. Aber das die News an sich unter $news['News'] zu finden ist verstehe ich nicht. Selbst wenn man diese Dinge als eine Hashmap ausgibt kann man diese besser gestalten.

Und das was die find Methode liefert keine Objekte sind haben diese keine eigenen save() und remove() Methoden. Diese sind über das Modell erreichbar, was diese Operation immer auf die zuletzt zugewiesene Id anwendet. Absolutes Gegenteil von intuitiv.

Ein weiterer Nachteil ist, das es kein Lazy-Loading gibt. Alle dazugehörigen Beziehungen werden aus der Datenbank ausgelesen, auch wenn man diese gar nicht braucht.

Cake stammt aus der PHP4 Zeit, obwohl es ein Jahr nach dem Erscheinen von PHP5 entwickelt wurde. Leider scheint mir dies der Grund zu sein für dieses schlechte ORM. Ich habe zumindest keinerlei Verständnis dafür.


Für node.js benutze ich Mongoose als ActiveRecord. Ein Schema zu definieren ist darin auch sehr einfach:

var Project = new Schema({
	  title: {type: String, required: true}
	, url: {type: String}
	, description: {type: String, required: true}
	, image: {type: String}
	, _user: {type: ObjectId, required: true}
});

Da MongoDB eine JSON basierte Datenbank ist, kann man direkt in den Objekten Unterobjekte speichern oder Arrays von Objekten. Dennoch ist es in einigen Situationen sinnvoll hasOne Beziehungen zu modellieren. Mongoose hat leider keine eingebaute Fähigkeit dazu. Es existieren einige Projekte dafür aber bisher hat mich keines überzeugt. Ich habe lieber selbst eine einfache hasOne Beziehung modelliert:

var users = [];
var anonymous = new User();

Project.virtual("user").get(function () {
	return this.__user;
}).set(function (user) {
	this.__user = user;
	this._user = user;
});

Project.pre("init", function(next, doc) {
	if (!doc._user)
		return next();
	//if (doc._user && users[doc._user])
	//	return next();
	User.findById(doc._user, function(err, user) {
		if (err || !user)
			return next(err);
		users[user.id] = user;
		return next();
	});
}).post("init", function() {
	this.__user = users[this._user] || anonymous;
	delete users[this._user];
});

Leider war dies um einiges schwieriger als ich gedacht habe. Ich definiere mir also zwei Funktionen die vor und nach dem erstellen des Objektes ausgeführt werden. Dabei ist die erstere Asynchron, also wird diese durch einen Callback beendet. In dieser hole ich mir mein verwandtes User Objekt aus der Datenbank und speichere dies in einem globalen Objekt zwischen, da diese neu hinzugefügten Objekte leider nicht übernommen werden wenn das tatsächliche Objekt erzeugt wird. Dies füge ich im zweiten Schritt an und lösche die globale Kopie, das diese nicht für die Laufzeit des Programms den Speicher zumüllt. Dann habe ich noch an meinem Modell eine Virtuelle Eigenschaft mit der ich das zuweisen eines neuen Users richtig behandeln kann.

Ich habe zwar lange für diese Lösung gebraucht, elegant ist sie nicht aber ich kann sie einfach verallgemeinern und auslagern. Zu benutzen ist es auch einfach.

Ein Problem gibt es allerdings: Da ich mit Callbacks arbeiten muss fällt Lazy-Loading leider weg. Eine getUser(callback) Methode zu definieren ist dabei keine Lösung. Die Views in node/Express sind synchron, können also keine asynchronen Methoden benutzen die auf Callbacks basieren. Damit muss ich nicht nur auf das bereits erklärte Pull-Modell verzichten bei dem sich die View selbst die zu darstellenden Daten holt, sondern auch auf das dynamische Laden verwandter Daten bei deren Zugriff.

Ich will die Kopplung von Controller und View niedrig halten. Mein Controller soll nicht wissen ob die View nun auf project.user zugreift oder nicht. Also muss ich zwangsläufig project.user vorher laden, denn die View kann dies nicht nachträglich machen.


Insgesamt hat node.js mit Express einige Dinge die mir aus meiner Erfahrung mit dWing fehlen und auch performancekritisch sein können. Pull-Views sind nicht möglich, ebenso wenig das dynamische laden von hasOne, hasMany und ähnlichen Beziehungen. Wobei es dank MongoDB auch viel weniger von diesen gibt. Diese werden nicht wie bei SQL durch die Normalisierung erzwungen sondern nur wenn sie sinnvoll sind. Daher gibt es auch viel weniger.

Die wirklich exzessive Verwendung von Callbacks in node ist sehr gewöhnungsbedürftig, aber ich habe mich schon daran gewöhnt. Mich nervt nur das was ich bereits angesprochen habe: Das ich im Controller wirklich alles laden muss was die View braucht. Und das ich dies tatsächlich im Vorfeld machen muss, auch wenn ich es in der View möglicherweise nicht brauche. Dies kann mir unter Umständen die Performance zusammenhauen.

Dennoch bin ich voll auf den Node Zug aufgesprungen. JavaScript ist einfach eine viel bequemere Sprache. So langsam kotzt mich PHP echt an mit den $dollar Variablen, den $array['Klammern'] anstatt der viel einfacheren Punkt.Notation wie in JS. Auch das komplizierte ausschreiben von array('eigenschaft' => 'wert') ist viel nerviger als einfach {eigenschaft: 'wert'}. Aber jetzt wo ich wieder mein dWing angesehen habe finde ich die sehr ausgereiften Lazy-Loading Methoden von PHP sinnvoll, und das es tatsächlich Klassen mit Interfaces hat.

Web-Frameworks: Push/Pull

Im letzten Eintrag habe ich schon erwähnt das die Templates in dWing mehr Fähigkeiten haben. Ein Weg MVC Anwendungen zu machen, und wie es CakePHP tut und express gezwungenermaßen auch ist, dass der Controller bzw. die Ressource die darzustellenden Daten an die View übergibt. Das nennt man das Push-Modell.

Persönlich halte ich dadurch die Trennung von Controller und View einerseits zu stark, denn die View weiß nichts über den Controller, andererseits zu stark, denn der Controller muss alles über die View wissen um die darzustellenden Daten liefern zu können.

Was ist wenn auf jeder Seite, also anders gesagt bei jedem Controller oder jeder Ressource etwas dynamisches in der View dargestellt werden soll. Ich kann entweder in jedem Controller diese Sachen der View übergeben was zu sehr viel redundantem Code führt. Oder aber ich übergebe dies an einer zentralen Stelle. In Cake im AppController, oder in Express in einem Helper.

Dies ist aber auch nicht optimal: Sollte die View die Daten doch nicht benötigen ist es unnützer Overhead der das ganze verlangsamt.

bei dWing gehe ich einen gemischten Weg. Die Ressource kann einerseits die Objekte an die View übergeben, pushen. Oder aber die View holt sich selbst was sie braucht, pull. Somit kann ich die Views ändern ohne den Rest der Anwendung anzugreifen. Es besteht also eine geringere Kopplung und Abhängigkeit der View von dem Controller. Außerdem halte ich meine Controller sauber, und schlussendlich bleibt das ganze auch performant.


Dies kann man leider leider in Express mit node.js nicht machen. Mehr dazu im Teil über Lazy-Loading und Async Callbacks.

Web Frameworks: Model, View und Controller

In letzter Zeit beschäftige ich mich wieder mehr mit Web Frameworks, da ich auf der Arbeit mit CakePHP arbeiten muss und nebenbei node.js und Express lerne. Und natürlich existiert noch mein dWing, das im Prinzip meine Meinung zum Thema Web Frameworks in Code gießt.

Ich habe mir vorgenommen in einigen Beiträgen zu den Themen Object-Relation-Mapping (ORM) bzw ActiveRecord, Push/Pull-Modell, Lazy-Loading und Asnyc Callbacks zu sprechen.


Heute geht es allerdings um Model-View-Controller. Dieses Entwurfsmuster ist dazu gedacht die Speicherung der Daten, die Logik und die Darstellung zu trennen. Persönlich finde ich die Logikschicht absolut überbewertet. Ich finde in modernen Anwendungen sollte es sich mehr um die Grundobjekte drehen und was ich mit diesen machen kann. Und auch dort ist weniger mehr. Denn eigentlich muss ich nur vier Operationen machen können. Diese auslesen (read, GET), erstellen (create, POST), bearbeiten (update, PUT) und löschen (delete, DELETE). In Klammern habe ich jeweils die Begriffe aus CRUD und den HTTP Methoden angegeben.

Im Grunde genommen geht es nicht darum was ich mache, sondern mit welchem Objekt ich dies tue. Bewerte ich einen Eintrag auf dWing dann gibt es keinerlei bewerte Operation auf meinen Einträgen. Stattdessen erstelle ich eine Bewertung zu diesem Eintrag. Somit habe ein weiteres Objekt, auf das ich eine der vier Grundoperationen (erstellen) anwende. So einfach ist dies.

Mein dWing ist genau nach diesem Muster aufgebaut. Meine Ressourcen implementieren einfach das RESTful Interface und alles andere geschieht automatisch. In Express gibt es das express-resource Modul, welches auch automatisch die Resource laden kann. Sehr fein :-) Bei CakePHP sehe ich keine solche Ressourcenorientierung. Dort werden die URLs nach dem /controller/methode Muster interpretiert. Andere Routen können definiert werden aber ich halte dies für recht unbequem.

Statische Seiten werden dort auch über einen Controller geleitet. Bei dWing geschieht dies automatisch im normalen Routing. Kein großer Unterschied. Bei Express musste ich mir selbst auch einen Router basteln der auf ein statisches Template umleitet.

Die Template Systeme von Cake und Express folgen dem Layout Prinzip. Also ist ein Layout Template definiert, dem z.b. als body variable der Inhalt übergeben wird. In dWing inkludiert jedes Template Kopf und Fuß einzeln. Etwas umständlicher, allerdings wird es so möglich das ich den Inhalt des Titel Elementes in meinem statischen Template definiere bevor ich den Kopf einbinde. In Cake und Express ist es nicht möglich solche Variablen vom Template zum Layout zu übergeben. Dafür muss ich mir noch etwas überlegen.

Bei dWing haben die Templates allgemein mehr Fähigkeiten, mehr dazu im Teil Push/Pull und ActiveRecord.

Never change a running system?!?

Da mein Laptop sowohl unter Linux als auch Windows ziemlich träge lief habe ich ihn nun eingestampft um neu aufzusetzen. Eigentlich hatte ich dies mi Februar schon vor aber habe es doch noch etwas verzögert um eine stabilere Ubuntu Version zu erwischen, die aktuelle Nightly von 11.04 alias Natty Narwhal.

Nundenn, gleich vorweg: Das Vorhaben war ein Misserfolg sonder gleichen.


Windows: Die Installation war gut gelungen, hätte mir auch nichts anderes erwartet, das System läuft nun schneller als Vorher, das war auch meine Absicht. Mit den Treibern gibt es allerdings leichte Probleme. Die Synaptics Treiber für mein Touchpad von der Acer Seite lassen sich nicht installieren, sie behaupten mein Touchpad würde nicht unterstützt werden. Die neueste Version von der Synaptics Seite installiert stattdessen Srybe, was auf Gesten auf Multitouchpads ausgelegt ist. Ich habe also ein wie gewohnt hässliches System Icon was ein noch hässlicheres Einstellungsfenster für die Gesten hat die ich sowieso nicht verwenden kann denn mein Touchpad hat keine Unterstützung für mehrere Finger. Das wirklich einzige für das ich die Treiber gebraucht hätte, das Scrollen am rechten Rand, geht trotzdem nicht. So ist das System zum surfen im Web nicht geeignet, aber dazu benutze ich Windows ja eh nicht.


Nun zu Linux bzw. Ubuntu, was ein viel größeres Epic Fail darstellt. Da ich ein System mit Komplettverschlüsselung haben will bin ich auf die Alternate CD angewiesen die einen textbasierten Installer mitbringt. Ich erstelle einen USB Stick und will diesen starten. Die Sprachauswahl beim boot geht, aber sobald ich mit der Installation beginnen will kommt ein schwarzer Bildschirm und nichts passiert. Schnell ausprobiert das dies mit einer älteren Alternate CD auch der Fall ist. Begründen kann ich dies nicht, aber als Lösung habe ich nun 10.04 installiert und inkrementell zuerst auf 10.10 dann auf 11.04 aktualisiert.

Dann das wichtigste: Die Installation der Grafiktreiber. Es wird der Nvidia und der experimentelle Nouveau Treiber angeboten. Klarstellend muss ich sagen das sowieso Nouveau im Einsatz ist, allerdings ohne 3D Unterstützung. Nvidia Treiber installiert, funktionierte bisher immer Wunderbar, Neustart und dann kriege ich beim Hochfahren einen schwarzen Bildschirm. Ich wusste nicht was los ist aber nachdem ich einfach mal probierte mein Verschlüsselungspasswort blind einzugeben startete das System normal, allerdings von 3D Unterstützung keine Spur. Ich schaue im Kernel Log nach und dort steht meine Grafikkarte würde nicht unterstützt werden, dabei steht im Readme explizit das sie unterstützt wird. Dann habe ich schnell den experimentellen Nouveau 3D Treiber ausprobiert, auch ohne Erfolg.

Im Endeffekt habe ich durch die Installation des Nvidia Treibers statt einem Eingabefeld für das Verschlüsselungspasswort nun einen schwarzen Bildschirm und darf dies blind eintippen, außerdem habe ich eine zu niedrige Auflösung durch die das Bild gestreckt und hässlich ist, dazu ist das Scrollen in Anwendungen insbesondere Firefox extrem langsam und unbenutzbar. Videos schauen brauche ich gar nicht probieren, dadurch läuft die CPU auf Vollgas und ruckelt trotzdem. Das schlimmste auch noch: Durch mangelnde Stromsparfunktion läuft die Grafikkarte immer im höchsten Takt obwohl sie keinerlei Arbeit leistet sondern die CPU alles machen muss. Dadurch läuft der Laptop heiß und zuzelt die Batterie leer. Außerdem funktioniert der Schlafmodus nicht richtig, er wacht einfach nicht daraus auf.

Ich kann also kaum Web surfen wegen des Scrollings, kann keine Filme schauen, kann den Laptop nicht für den mobilen Einsatz benutzen.


Und die ganze Scheiße nur wegen geschlossenen Treibern. Unter Windows Synaptics, unter Linux Nvidia. Mein aktueller PC hat AMD Grafik, eigentlich dachte ich das die 3D Treiber noch etwas nachhinken aber ich bin gerade positiv überrascht, dass ich das Liveimage einfach mit 3D und Unity benutzen kann, ohne irgendwelche dummen Treiber oder sonstwas.

 Also nächsten mal sicherlich AMD auf dem Laptop und weniger Stress.

Ist OpenID tot?

Durch meine Mitarbeit an der node-openid Bibliothek und der Gestaltung meiner neuen Webseite bin ich mal wieder intensiv in den Kontakt mit OpenID gekommen.

Nun bin ich über einen sehr guten Artikel gestolpert der die Frage stellt, ob OpenID nun tot ist. Ganz provokant wird OpenID als der erfolgreichste Fehlschlag des Webs bezeichnet.

Auch aus meiner Sicht ist sicherlich etwas falsch gelaufen. Die auch im Artikel angesprochene Asymmetrie ist überdeutlich: fast jeder Internetnutzer hat wahrscheinlich ohne es selbst zu wissen schon mehrere OpenIDs, dennoch gibt es extrem wenige Webseiten bei denen man sich mittels OpenID anmelden kann. Nur die wenigsten setzen ganz auf OpenID, so wie ich.

Der Grund warum ich es einsetze ist einfach: Ich habe weder Lust noch Fachwissen um hundertprozentige Sicherheit zu gewährleisten. Die billigsten Webseiten speichern einfach die Passwörter im Klartext in der Datenbank, andere benutzen dafür Hashwerte die je nach benutzter Hashfunktion und Salt auch relativ unsicher sind. Ein offizielles SSL Zertifikat kann sich keine Privatseite leisten, über ein selbst signiertes wie ich es benutze schreit der Browser standardmäßig. Dadurch ist das Passwort meist unverschlüsselt im Netzwerk unterwegs. Für die Persistenz kann man auch noch mehr oder weniger unsichere Cookies verwenden.

Alles in allem: Echte Sicherheit zu gewährleisten ist schwer und mit OpenID kann ich all dies an den OpenID Provider delegieren. Das Benutzerpasswort kriege ich nie mit, das einzige was meinen Benutzer identifiziert ist seine OpenID, und die ist sowieso öffentlich. Diese wird auch für Persistenz benutzt und im Cookie gespeichert. Auch hier kann das Cookie gern über XSS oder sonstige Methoden gestohlen werden, es sind ja keine sensitiven Informationen darin enthalten.


Zurück zum Thema: Es gibt zu wenige Seiten bei denen man sich mittels OpenID anmelden kann. Zudem ist das Protokoll auch übermäßig komplex wie ich bei meiner Arbeit an der Bibliothek feststellen musste.

Der Artikel geht aber noch auf ein anderes Problem mit OpenID ein: Der Standard ist für die Authentifizierung gedacht, das heißt sicherzustellen, das dem Benutzer die eingegebene OpenID (z.b. swatinem.de) wirklich gehört.

Über Erweiterungen wie Simple Registration (Sreg) oder Attribute Exchange (AX) kann die Webseite weitere Daten wie beispielsweise E-Mail Adresse und Name anfordern. Diese Erweiterungen sind optional und es ist nicht sichergestellt, das der OpenID Provider diese Informationen auch liefert, also muss man so oder so eine Registrierungsmaske bereitstellen.

Nebenbei Erwähnt: Sreg definierte nur wenige Attribute und wurde daher durch AX ersetzt. AX stattdessen setzt auf absolut flexible an RDF angelehnte URIs als Attribut Schlüssel und somit nicht einheitlich, zu unübersichtlich und zu kompliziert.


Ich als technisch versierter Nutzer kann einerseits nicht nachvollziehen warum so viele Webseiten unnötig viele Nutzerdaten haben wollen, andererseits will ich absolute Kontrolle darüber haben welche Webseite welche Daten von mir hat. OpenID bietet mir genau das.

Als Alternative ist die Anmeldung über Facebook sehr beliebt, nicht allerdings bei mir. Möglicherweise ist das zugrundeliegende OAuth ein einfacheres und besser Protokoll als OpenID, allerdings nerven mich zwei Sachen: erstens das die Webseite von Facebook anscheinend beliebig viele Daten erhalten und ausnutzen kann. Andererseits, weshalb ich die Anmeldung über Facebook nicht implementieren werde: Für die Anmeldung über Facebook ist es erforderlich das die Webseite bei Facebook eingetragen ist, und dazu benötigt man ein Facebook Konto. Dieser Netzwerkeffekt und Anmeldezwang ist es was ich an Facebook am meisten verabscheue und gegen was ich auch weiterhin ankämpfen werde.

Ich will einfach ein dezentrales System wie E-Mail, Jabber, OpenID und möglicherweise Diaspora bei dem jeder miteinander agieren kann egal welchen Dienstanbieter er benutzt. Was wäre bitte E-Mail wenn ich mit meiner Googlemail Adresse nur an Googlemail senden könnte und nicht an GMX oder Hotmail? Dieser Zwang zu einem Dienstanbieter und dadurch dessen Monopolisierung ist meiner Meinung nach eine der größten Gefahren für die Freiheit im Web und muss daher dringend entgegengewirkt werden.


older posts