Auf Wunsch beschreibe ich im Folgenden, was ich mit diesem Satz über dsnblog meinte:
Dazu kam der Versuch, eine bestimmte Form der objektorientierten Programmierung durchzuziehen: ohne Managerklassen, und mit einem kleineren Controller durch Initialisierung bei Objekterstellung in den Objekten selbst
Dazu muss ich ein bisschen ausholen, denn ohne Kenntnis des Hintergrunds ergibt das alles wahrscheinlich wenig Sinn.
Ich habe Informatik studiert, aber Programmieren schon vorher gelernt - auch wenn ich im Studium natürlich viel dazugelernt habe. Natürlich wurden wir im Studium mit Patterns konfrontiert, aber von denen war und bin ich nicht überzeugt. Klar, ein Singleton kann nützlich sein, und das benutze ich auch. Aber Dinge wie eine Factory oder eine Factory-Factory bringen mich dazu, schreiend davonzurennen. Vielleicht ist das gar nicht so unüblich für Menschen, die Programmieren größtenteils mit Bash gelernt haben…
Ich bin überzeugt, dass viele der Patterns - so wie man sie an der Uni lernt zumindest - dazu führen, dass der produzierte Code tausendmal schlechter ist, als wenn ein halbwegs ordentlicher Programmierer sich auf seine Intuition verlässt. Ich muss wohl hinzufügen, dass ich mich für einen nicht besonders guten Programmierer halte, daher schon länger mir Hilfen zusammensuche, um meine Intuition zu ergänzen.
Meine Ablehnung von Pattern schließt das MVC-Pattern ein, aber nur teilweise. Denn ich finde die Grundidee gut: Die Präsentation von der Logik trennen, das macht meistens Sinn. Aber die konkrete Ausgestaltung des Patterns variiert in jeder Implementation, es ist meiner Wahrnehmung nach das meistbenutzte Pattern überhaupt, das keine zwei Programmierer im Universum gleich verstehen. Bedeutet für mich: Wenn ich meinen Code für meine Projekte halbwegs ordentlich strukturieren will, muss ich meine eigene Architektur schaffen, die ich für gut erachte - denn am MVC kann ich mich nur an der Grundidee grob orientieren.
Das ist die Ausgangslage: Gute Architektur durch Trennung von Datenbank-Ebene, Code-Ebene und Präsentation, dann ein klar definiertes Kontrollschema, und das ganze so objektorientiert wie sinnvoll, das ist das Ziel.
Fehlende Managerklassen
Einer dieser Grundpfeiler meiner eigenen Architektur ist die Database-Klasse, bei meinen Ruby-Projekten in der database.rb. Meistens benutze ich sqlite, aber ich möchte meinen Code schon so schreiben, dass ich die Datenbank leicht auswechseln kann. Deshalb beinhaltet die Klasse eine Reihe von Funktionen, mit der auf die Daten zugegriffen wird - beispielsweise eine addEntry(entry)
. Dem Rest des Programms ist es dadurch egal, was genau die Datenbank da macht, und beinhaltet keinen einzigen Fitzel SQL.
Nur: Wer spricht mit der Datenbankklasse?
Die erste Version meines Hardwareempfehlers war ein Java-Programm. Auch dort gab es diese Database-Klasse, aber zusätzlich gab es eine zweite Zwischenebene: Die Warehouse-Klasse, eine Managerklasse, die beispielsweise die gesamte Hardware unter einem bestimmten Preis holte, sortierte und in passender Form für den Recommender aufbearbeitete. Ein Request eines Nutzers ging also erst an den Server, dann an den Recommender, dann an das Warehouse, dann an die Datenbank, dann konnte der Recommender arbeiten, und schließlich der Server die Seite rendern. Das gefiel mir nie so richtig, schon weil das Warehouse immer mehr an Funktionen in sich sammelte und noch dazu die Klassen der Parameter (Hardware, Cpu oder Gpu? Oder Hardware samt generic?) irritierend wurden.

Ich glaube, man sieht leicht: Das war weder schön noch simpel noch effektiv. Und das ist sogar noch unvollständig. Einfach keine gute Architektur.
Dann stolperte ich über diesen Ratschlag:
Don't make objects that end with 'er'.
Und ich sponn das weiter. Es geht mir nicht um die Benennung, und ich glaube, es geht auch den Autor des Artikels nicht um die Benennung. Es geht darum, Objekte zu bauen, die mehr sind als ein Datenbehälter mit vielleicht ein-zwei Funktionen, die von einer Managerklasse gesteuert werden. Es geht darum, komplette und lebensfähige (self-sufficient) Objekte zu bauen, die ihre Funktion soweit möglich komplett beinhalten.
Mit Ausnahme von Dingen wie der Trennung zwischen Anwendungsebene und Datenbank, natürlich.
Für den Hardwareempfehler fiel dadurch die Warehouse-Klasse weg, und der Recommender wäre das nächste Ziel, wollte ich konsequent sein. Das war zuviel Aufwand, aber für dsnblog konnte ich es von Anfang an beherzigen, daher ist das näher an dem Ideal.
Dementsprechend gibt es in dsnblog keine "EntryManager"-Klasse, die dafür sorgt, dass die Entry-Objekte mit den richtigen Daten initialisiert und geordnet werden und wann sie in der Datenbank gespeichert werden und wann nicht. Teilweise spielt da zwar die server.rb rein, der Controller, aber generell ist damit gemeint, dass natürlich die entry.rb eine Funktion sendTrackbacks(request)
hat, statt dass eine Trackbackmanagerklasse einen Entry - oder gar eine Liste von Links - bekommt und die Trackbacks dann absendet.

Besser.
Das ist ein fortwährender Abwägungsprozess. Folgt man dem Motto zu arg, kann es passieren, dass Funktionalität doppelt geschrieben wird. Oder die Erweiterbarkeit nicht so einfach gegeben ist. Beispiel dafür ist meine Markup-Sprache: Momentan ist das auch im Entry, und man kann durchaus argumentieren, dass das auf die Template-Seite gehört, und natürlich muss das für Kommentare auch noch kopiert werden. Andererseits ist es doch desöfteren so, dass es da kleine Unterschiede gibt - Kommentare müssen z.B. keine Bilder einbinden können, sollten es sogar nicht können. Also ist es vielleicht gar nicht so schlecht, wenn sie direkt ihre eigene Formatierungsfunktion dafür bekommen. Wie gesagt, ständiger Abwägungsprozess.
Bei dsnblog war ich im Endeffekt sehr glücklich mit dem Ergebnis dieser Regel, weil es dazu führte, dass ich neben database.rb und server.rb nur die Kernklassen hatte: Eine Klasse Entry und eine Klasse Comments, dazu eine Klasse CommentAuthor (die ich im Nachhinein nicht so toll finde). Genau das, woraus ein Blog besteht, und alles, was in der Datenbank gespeichert und in der View abgebildet werden muss.
Schlanke Controller
Eine weitere Konstante meiner momentanen Architektur ist die server.rb. In Ruby/Sinatra ist die bei der klassischen Variante wohl generell üblich, aber ich hatte genau so eine Klasse auch schon im Java-Code. Für mich ist sie der Controller: Hier wird festgelegt, was bei Aufruf welcher URL passiert. Dazu kommen ein paar Hilfs-Funktionen und der Cache ist hier implementiert, aber URL->Funktionalität, das ist die Grundaufgabe.
Diesen Controller schlank zu halten ist eine stete Herausforderung. Denn es ist ziemlich verlockend, relativ viel Funktionalität einfach hier zu erledigen. Aber zum einen könnte man dann auf Objekte gleich verzichten, und man schafft einen Blob, eine viel zu große Sammlung an Code.
Stattdessen versuchte ich in dsnblog, alle Funktionalität auszulagern. Ein Beispiel:
post '/addEntry' do
protected!
entry = Entry.new(params, request)
redirect "/#{entry.id}/#{URI.escape(entry.title)}"
end
Das ist der gesamte Code des Controllers, um einen Artikel anzulegen. Es wird einfach ein Entry-Objekt erstellt, und das Objekt weiß selbst, was es zu tun hat. Das funktioniert natürlich besonders gut in Verbindung mit dem Verzicht auf Managerklassen von oben, aber auch mit Managerklassen wäre das möglich - die Daten könnten ja auch einer EntryFactoryManagerFactory übergeben werden.
Ruby macht das nicht unbedingt ganz einfach, denn die entry.rb soll darauf reagieren, wieviele Argumente es erhält. Bekommt es nur eins, weiß die Klasse, dass es eine ID bekommen hat, und kann sich mithilfe der ID aus der Datenbank initialisieren. Bekommt es zwei, weiß sie, dass es um einen neuen Eintrag geht. Aber Ruby kann keine Funktionen überladen, auch nicht den Konstruktor. Deshalb muss ich den so definieren:
def initialize(*args)
case args.length
when 1
initializeFromID(args[0])
when 2
# creating entry from params and save in database
…
end
end
Muss man übrigens auch nicht schön finden, das über die Anzahl der Argumente zu steuern, aber alternativ könnte man ja auch den Inhalt des Arguments nehmen, wie das beispielsweise die Preview derzeit macht. Wobei die Ansätze zu mischen an sich unschön ist, aber ich brauchte einen Toggle, um das Abspeichern in der Datenbank und Absenden der Trackbacks zu verhindern und trotzdem den Konstruktor nutzen zu können.
Ich habe mich da übrigens nicht immer konsequent dran gehalten. Die Datei-Hochladenfunktion ist beispielsweise komplett in der server.rb implementiert, weil es dazu keine passende Klasse gibt, oder ich sie nicht sah, und der nötige Code so kurz ist.
Aber trotz der Inkonsequenz: Das sofortige Weiterreichen der Eingabedaten an die Konstuktoren der Objekte, statt leere Objekte zu konstruieren und dann im Controller zu befüllen und am besten dort noch zu managen (z.B. indem sie danach der Datenbank zum Speichern übergeben werden), führt dazu, dass die server.rb mit ihren 400 Zeilen vertretbar schlank ist.
Fazit
Ich kann das ganze auch kurz fassen: Ich suche immer noch nach einer guten Standardarchitektur für Webanwendungen, und fand es bei dsnblog sinnvoll, Logik statt in Managerklassen in die Objekte selbst zu legen und den Controller Daten einfach nur durchreichen zu lassen, sodass die Objekte sich auch noch selbst initialisieren müssen. Die so entstehende objektorientierte Architektur gefällt mir gut, insbesondere für einen Blog, dessen Einträge und Kommentare in dieser Form schön abbildbar sind.
Ich bin dabei wahrscheinlich beeinflusst durch eine Reihe von Artikeln, über die ich durch HN gestolpert bin, die ich aber nicht aufzählen kann. Wobei viele auf HN vorgestellte Projekte das sowieso völlig anders machen.
onli blogging am : Performance-Tuning einer Webapp mit Index und Arbeitsvermeidung
Vorschau anzeigen