Sven Rautenberg: Web-Programme modular aufbauen

Beitrag lesen

Moin!

Und wenn man etwas schneidet, dann ... blutet es? Nein, dann hat man Schnittstellen!
Hm. Ich dachte man hat ein Programm, welches halt modular aufgebaut ist(was auch immer das jetzt heißt), und das komplette Programm hat Schnittstellen um mit der Außenwelt kommunizien zu können, das dachte ich immer.

Schnittstellen sind so häufig, dass du gar nicht merkst, dass du sie benutzt. Die Parameter einer Funktion bilden eine Schnittstelle. Denn es ist durch die Funktion fest definiert, a) in welcher Reihenfolge b) welche Daten kommen müssen, und ob c) einige optional sind bzw. d) Default-Werte haben. Ebenso ist e) definiert, welches Format das Funktionsergebnis hat.

Diese für Funktionen gültige Aufzählung a) bis e) kannst du natürlich auch auf Datenbanken übertragen:
a) Die Reihenfolge spielt keine Rolle, aber du mußt wissen, welche Spalten du beschreiben oder lesen willst.
b) Das Datenformat (String, INT, ENUM) muß natürlich bekannt sein.
c) Du könntest einige Werte freilassen wollen, weil der Benutzer sie nicht angegeben hat.
d) Diese Werte können einen Default-Wert haben.
e) Ähm - die Datenbank hat natürlich auch ein Funktionsergebnis: Die Erfolgsmeldung - oder die Fehlermeldung. :)

Du siehst also: Die gesamte Computerwelt besteht fast nur aus Schnittstellen, weil sich fast alles mit Funktionen erledigen läßt. Wenn du deine eigenen Funktionen definierst, definierst du deine eigenen Schnittstellen. Diese Schnittstellen _sinnvoll_ zu definieren ist nicht so leicht. Wenn man "mal eben" eine Funktion erstellt, dann tut die wahrscheinlich, was man von ihr will. Aber bestünde nicht die Möglichkeit, dass man noch andere Dinge von der Funktion will? Die Wiederverwendbarkeit der Funktion ist besser, wenn sie eine geeignete Schnittstelle besitzt.

Gut, aber darunter kann ich mir jetzt _nichts_ vorstellen. Was wäre das denn praktisch? Für mich ist eien Schnittstelle eher ein Programmteil, oder eigenes Modul, welches z.B. einen Daten-Export oder Import erledigt, oder die Anbindung an ein anderes Backend-System. Das ist dann halt ein Scrip welches entweder Dateien aus der DB generiert, oder Dateien parst und einfügt, oder ein komplette Kommunikation regelt. _Das_ ist für mich eine Schnittstelle. Interne Schnittstellen kann ich mir nur schwer vorstellen. Schnittstellen wozwischen? Zwischen den Modulen? Aber dazuwischen findet doch im Prinzip kein Datenaustausch statt, alle Module greifen auf dieselbe Datenquelle zurück.

Ich weiß nicht, was du programmieren willst. Insofern ist es schwierig, da irgendwas konkret zu sagen. Aber eines stimmt: Die einzelnen Module selbst haben mit anderen Modulen nicht zu kommunizieren, sondern nur mit dem Basismodul. Bzw. genauer gesagt: Mit ihrem Elternmodul. Denn es ist ja durchaus denkbar, dass du ein Ausgabemodul geschrieben hast, welches allgemein gehalten ist. Dieses Ausgabemodul kriegt jetzt Untermodule, die die Ausgabe als HTML, XHTML, LaTeX, PDF, GIF, ASCII etc. auf Bildschirm, Festplatte, Papier usw. erledigen können. Das Basismodul sorgt dafür, dass das Ausgabemodul die auszugebenden Daten kriegt. Das Ausgabemodul verwaltet die vorhandenen Submodule und reicht die Daten an das passende Modul weiter.

Bei diesem Punkt füllt mir auch ein - das sollte man vielleicht auch abkapseln, also die Schnittstelle zur eigenen Datenbank, denn zur Zeit verwende ich munter in allen Scripten mysql_funktionen, ich habe zwar mysql_qery() schon in eine zentrale Funktion gebannt, aber mysql_fetch_assoc(), mysql_num_rows()... das verwende ich noch alles einzelnd. Das solte ich vielleicht auch in Funktionen schreiben, vielleicht schreibe ich mir eine eigene DB-Kasse, die vorhandenen finde ich viel zu aufgebläht! Vielleicht gucke ich mir auch mal PEAR:DB an, mal schaun, oder hast Du einen Tipp? Eigentlich sollten einige Funktionen reichen: mysql_query, mysql_fetch_assoc, mysql_fetch_row, mysql_num_row, mehr verwende ich eigentlich gar nicht. Ich wüßte jetzt auch nicht als KLasse, ich verwende eh nur eine DB, also hat es keinen großen Sinn für eine DB-Connection extra eine Instanz zu erstellen, vielleicht für eine Abfrage, mal schaun. Wäre vielleicht sogar ganz gut, um ein wenig das Frontend von der Datenstruktur abzukoppeln, wenn ich dann einen Spaltennamen ändere mache ich das zentral in der Klasse und nicht mehr in jeder Abfrage! Ein gutes Argument ;-)

Definitiv. PEAR soll ja ganz nett sein - ich hab mich damit aber noch nie beschäftigt. Es ist jedenfalls eine _sehr gute_ Idee, wenn man schon modular arbeitet, dann die Datenbank-I/O auch über ein Modul regeln zu lassen. Dadurch bist du wesentlich freier in deiner Gestaltung. Auch wenn es derzeit nur ein MySQL-Modul geben wird, kannst du dennoch _im Prinzip_ jede andere Datenbank auch anbinden. Und die muß nicht mal eine echte Datenbank sein, denn solange du irgendwo speichern und lesen kannst und die Daten wiederfindest, würden auch Text- oder XML-Dateien funktionieren, um nur ein Beispiel zu nennen.

Und das würde sich dann sogar ganz gut machen, denn wenn du sowohl ein MySQL-Modul als auch ein XML-Modul geschrieben hast, kannst du deine MySQL-Datenbank mit einem Export-Modul einfach aus MySQL auslesen und in XML reinschreiben und hast so eine nette Export-Funktion. Auf einem anderen Server hast du vielleicht kein MySQL, sondern PostgreSQL, aber natürlich auch XML, und kannst so den Datenimport regeln. :)

Das schreibt und liest sich jetzt natürlich recht leicht - die zentrale Frage dabei ist immer: Wo docken die Module an? Im obigen Beispiel wäre es z.B. sinnvoll, wenn das MySQL- und das XML-Modul an einem DB-I/O-Modul andocken. Dort könnten dann auch die Funktionsmodule "Export" und "Import" andocken. Oder aber es dockt ein Modul "Kopieren" am zentralen Modul an und liest über den Weg I/O->MySQL Daten ein und schreibt sie über I/O->XML wieder weg. Die Frage ist dabei natürlich, wie man sowas geregelt kriegt. Wie meldet das I/O-Modul, dass es sowohl XML als auch MySQL anbieten kann? Und wie entscheidet das Kopier-Modul, von wo nach wo es kopieren will? Konkrete Aussagen gibts ohne konkrete Aufgaben nicht.

Nochmal zu den internen Schnittstellen, ein Problem ist z.B. das Menü. für jedes neue Modul brauche ich meist mind. einen Menüeintrag. Bisher habe ich eine Menüleiste, in der die Menüs als link fest reingeschreiben sind. Einen guten Ansatz habe ich in einem featured Artikel zu einem Javascript-Menü mit Baustruktur gesehen, da wurden die einzelnen Menüpunkte in Funktionsaufrufe mit einer ID und einer Parent_ID geschreiben, um am Ende daraus eine schöne Baustruktur zu machen. Das könnte ich ja so übernehmen, vielleicht mache ich ja direkt ein Menü mit Untermenüs also Aufklappmenü. Dann schreibe ich am besten die Menü-Punkte mit einer ID und einer Parent_ID in eine MySQL Tabelle, um die Struktur einfach verändern zu können, und kann dann daruas beim "Installieren" eines neuen Moduls daraus jedesmal die "menue.inc"-Datei neu zu generieren. Das wäre für den Live-Betrieb vermutlich erheblich besser als jedesmal eine DB oder Flat-File abzufragen und daraus zur Laufzeit das Menü zu generieren.

Meine Vorstellung ist, dass es einen gewissen Installationsvorgang für jedes Modul geben sollte, der das Modul im System bekannt macht. Entweder einmal statisch beim wirklichen Hinzufügen des Moduls, oder immer wieder dynamisch beim Benutzen des Moduls. Die erste Methode würde z.B. darin bestehen, in einer zentralen Datei eine Zeile hinzuzufügen, die das Modul anmeldet. Die zweite Methode wäre dagegen zwar performance-fressender, aber immer auf dem aktuellen Stand, was die Module angeht.

Menüs sind zum Beispiel nicht fest codiert, sondern werden durch deine Module definiert. Das bedeutet, dass beispielsweise jedes Modul eine Funktion besitzen könnte, die einen Menüeintrag zurückliefert,
Aber wie mache ich das? Das ist wie im obigen Beispiel eine eigene Datei. Wie mache ich jetzt der Menüleiste klar das diese Datei ab heute ebenfalls abgefragt werden soll? Und woher weiß ich vorher wie die Funktion heißen wird?

Entweder bindest du immer alle vorhandenen installierten Module ein, und bei der Einbindung liefern diese Module durch eine Installationsfunktion die notwendigen Angaben für die zentrale Registrierung, oder bei der Installation der Module wird eine zentrale Datei entsprechend erweitert, in die das Modul und alle dazugehörigen, allgemeinen Daten (wie z.B. Menüeinträge) gespeichert werden. Oder jedes Modul besitzt eine Funktion, welche z.B. die Menüeinträge zurückliefert.

Mehrsprachige Seiten habe ich bislang so realisiert, dass es je Sprache ein Verzeichnis gibt (also /de/, /en/ und /fr/), in denen sich immer identische Dateien befinden.
Was bei statischen Seiten prima ist, für dynamische eher ungeeignet, oder?

Nein, warum? Auch dynamische Seiten können damit arbeiten. Notfalls greift ein globales URL-Mapping mit mod_rewrite, welches den Sprachbestandteil der URL als Parameter ummodelt.

Ich weiß nicht, was hier die ideale[tm] Lösung für URLs ist. Die Sprachinformation kann dort hineingehören - sie muss es aber auch nicht, denn nicht zuletzt in HTML gibt es das lang-Attribut. Nur bringt das wohl nicht unbedingt den gewünschten Effekt.

Verläßt man sich hingegen ganz auf die Apache-Content-Negotiation, kann man als Besucher die Sprache nicht mehr wechseln. Es gibt deswegen nur zwei praktikable Methoden, die Information der gewünschten Sprache sicher zu übermitteln: Entweder als Teil der URL (im Pfad oder als Parameter), oder in Sessions bzw. Cookies (was prinzipiell das Gleiche ist - und schlecht obendrein).

Als Startseite im Hauptverzeichnis ist ein Skript aktiv, welches den Accept-Language-Header vom Browser auswertet und entsprechend auf eine der verfügbaren Sprachen weiterleitet. Auf diese Weise wird eine sinnvolle Vorauswahl getroffen, die der Benutzer jederzeit ändern kann - vorzugsweise wird er dies wohl gleich zu Beginn tun.
Das würde ich auch so machen. Aber da man sich sowieso anmelden muß, werde ich die Sprach-Präferenz bei jedem User speichern.

Wenn du die gewünschte Sprache speicherst, musst du diese Information entweder bei jeder Seitenanforderung wieder nachgucken, oder sie beim Request mitschicken. Wenn du sie mitschickst, kann das wie oben geschildert in der URL geschehen. Mit dem Vorteil, dass der Benutzer schnell mal wechseln kann, und nicht seine Benutzereinstellungen aufsuchen muß.

Das Problem dürfte wohl die gedankliche Fassbarkeit der Dynamik sein.
Das habe ich gemerkt. Ein paar Punkte habe ich ja angesprochen, aber mehr fallen mir im Moment nicht ein! Kennst Du noch den ein oder anderen Punkt wo man drauf achten sollte?

Ich muß ehrlich sagen: Dein Projekt ist nach meinem Eindruck noch weit davon entfernt, auch nur in irgendeiner Weise modular zu sein. Da du es allerdings auch nicht näher erläutert hast, könnte dieser Eindruck daran liegen, dass das Projekt sich nicht unbedingt für den modularen Aufbau eignet.

- Sven Rautenberg

--
Diese Signatur gilt nur am Freitag.