MSP-Projektwoche-RSS-Atom

Aus THM-Wiki
Wechseln zu: Navigation, Suche
Dokumentation
Arbeitstitel MSP-Projektwoche-RSS-Atom
Kurs Methoden des Software-Entwicklungsprozesses
Semester WS 09/10
Teilnehmer Nils Braden, Stefan Bußweiler, Michael Eckel, Johannes Tietje, Florian Weber, Jochen Woidich
Programmiersprache PHP


Diese Wiki-Seite dient zur Präsentation der Ergebnisse des MSP-Projektwochen Teams "RSS/Atom-Feed for E-Study".

Team

Die Mitglieder des Teams in alphabetischer Reihenfolge

  • Braden, Nils (stellvertretender Teamleiter)
  • Bußweiler, Stefan
  • Eckel, Michael
  • Tietje, Johannes Florian (Teamleiter)
  • Weber, Florian
  • Woidich, Jochen Uwe

Die Gruppe trifft sich im Raum F113 sowie im IRC-Chat unter irc.freenode.net (#MSP-Projektwoche).

Vision Document

Mit diesem Projekt verfolgen wir zwei Hauptziele:

  1. Nutzern von eStudy erlauben, externe Feed-Quellen anzulegen (im Folgenden als Externe Feeds bezeichnet)
    • Es soll folgende Arten von Feeds geben:
      1. Globale Feeds (vom Administrator einstellbar)
      2. Kurs-Feeds (vom Kursdozenten/Moderator einstellbar)
      3. Benutzer-Feeds (vom jeweiligen Benutzer einstellbar)
    • Ein Benutzer soll bestimmen können, welche Feeds er angezeigt bekommen möchte
  2. Erschaffen einer Infrastruktur, um eStudy-Modulen zu erlauben, Feeds für die "Außenwelt" (also externe Betrachter) zu generieren (im Folgenden als Interne Feeds bezeichnet)
    • Der Programmierer eines eStudy-Moduls soll eine einfache Möglichkeit erhalten, nach außen sichtbare Feeds für das Modul zu erstellen

User Stories

Als Nutzer möchte ich...

Beschreibung Aufwand
meine eigenen Feed-Quellen hinzufügen und globale Feed-Quellen, wie Trac und MNI-Homepage, auswählen können. 3 Tage
globale Feeds aktivieren/deaktivieren. 1 Tag
benachrichtigt werden, falls mein neu hinzugefügter Newsfeed ungültig ist oder einer der existierenden Newsfeeds, die von mir erstellt wurden, ungültig geworden ist. 1/2 Tag
meine Feeds sortieren 1 Tag
die Anzahl der Einträge pro Feed setzen und bestimmen, welche Teile eines Feed-Eintrags und wieviel Text dargestellt wird. 1/2 Tag
wählen können, ob ich für Nachrichten den Newsletter oder den Feed nutzen möchte 1/2 Tag
mich bei Foren-Threads, Literatur und Kursen als Feed-Abonnent eintragen können 5 Tage
verschiedene Feed-Quellen zu einem Feed zusammenmixen können 1 1/2 Tage

Als Dozent/Moderator möchte ich...

Beschreibung Aufwand
kurseigene Newsfeeds anlegen und löschen, die jeder Nutzer des Kurses in seinem Profil als aktivierte Feeds auswählen kann. 1 Tag


Als Administrator möchte ich...

Beschreibung Aufwand
globale Newsfeeds anlegen und löschen, die jeder Nutzer in seinem Profil als aktivierte Feeds auswählen kann. 1/2 Tag
globale Feeds für alle Nutzer cachen können 2 Tage
nicht-kritische Feeds der Hauptseite hinzufügen, welche ohne Authentifizierung abgerufen werden können 1 Tag
sicherstellen, dass kritische Inhalte nur legitimierten Nutzern angezeigt werden (Evaluation der Authentifizierungs-Möglichkeiten dringend nötig, Know-How fehlt) 2 Tage

Planningpoker-Evaluation

Wir hatten viele kleine User-Stories, die im Prinzip wenig Arbeit erfordern, aber darauf basieren, dass eine gute Grundlage vorbereitet wurde. Ein zentraler Controller für RSS-Feeds müsste an allen Stellen, an denen Daten angeboten werden sollen, implementiert werden. Durch die kleinschrittigen Abstimmungen haben wir als Team etwas aus den Augen verloren, dass die Grundlage eigentlich vor allen anderen Stories bearbeitet werden müsste und haben dementsprechend hierfür keine Zeit eingeplant.

Außerdem wurde die Abstimmung auf Mannstunden ausgerichtet durchgeführt, sie sollte eigentlich jedoch auf Mannstage bezogen sein. Durch dieses Missverständnis (Gruppe wählt Mannsstunden, Außenstehende wählen Mannstage) kamen teilweise sehr unterschiedliche Ergebnisse zustande.

Eine weitere technische Hürde kam durch das Tool planningpoker.com zustande, da einige Benutzer Probleme mit der Sitzungsverwaltung hatten. Sitzungen wurden unterbrochen oder waren nicht mehr aktiv, wenn man die Vor-/Zurück-Buttons des Browsers verwendet hat oder parallel in einer anderen Planningpoker-Sitzung aktiv war. Es war zudem möglich, durch mehrfaches Klicken auf die gerade aktive Karte mehrere Stimmen unter einem Namen abzugeben, während der Datenaustausch mit dem Server noch aktiv war. Dies trat vor allem wegen der langsamen Internetverbindung auf.

Entwicklung

Info.png

Dieser Bereich dient der Ablage von Links und Hinweisen sowie der Dokumentation des Entwicklungsprozesses.

Tafel-Shots

Hier werden alle Bilder von den Tafeln abgelegt - wenn möglich auch mit dem Erstellungsdatum. Einzelne Bilder von den hier aufgeführten sind auch in den jeweiligen Kategorien dieser Wiki-Seite zu finden.


Tag 1 (05.01.2009)


Tag 2 (06.01.2009)


Tag 3 (07.01.2009)

Links

Externe Feeds

Diese Sektion beschreibt Feeds, welche von externen Seiten (heise.de, mni.fh-giessen.de) angeboten und im E-Study angezeigt werden können.

Gewünschte Features

  • Jeder User hat Einstellung zu Feeds im Profil
    • Deaktivieren von globalen und Kurs-Feeds
    • Einstellen eigener Feeds
    • Prüfen der neuen/vorhandenen Feeds
    • Benachrichtigung per PN falls Feed (dauerhaft) nicht verfügbar
  • Jeder Kurs kann durch Dozenten/Moderatoren Kurs-Feeds bekommen
    • In Kursen in denen Feeds gesetzt sind werden keine globalen Feeds angezeigt, User-Feeds haben Vorrang vor Kurs- und globalen Feeds
  • Das Foyer (also der globale Bereich) kann vom Administrator Feeds zugewiesen bekommen

Klassendiagramm

Data Access Object: Der Zugriff auf die Datenbank wird durch die Klassen "ExternFeedDAO" und "ManageUserFeed" gekapselt. Dadurch ist es möglich die Datenquelle beliebig zu verändern, ohne den aufrufenden Code anzupassen.

Implementierung

  • neues Modul: Feeds
    • Model-View-Controller
      • Feed-DAO
      • Feed-Controller
      • Feed-View
  • Konfiguration im Benutzerprofil
  • Konfiguration in den Kurs-Einstellungen
  • Konfiguration im Admin-Menü

Datenbank-Design

Es gibt 3 Arten von externen Feeds:

  • Globale Feeds
  • Kurs-Feeds
  • User-Feeds

Im Entity-Relationship-Diagramm stellt sich dies wie folgt dar: Eine Basisklasse namens ExternalFeed, welche gemeinsame Attribute enthält. Die abgeleiteten Klassen sind GlobalFeed, CourseFeed und UserFeed. Da es nicht möglich ist, Vererbung in einem relationalen Datenbanksystem zu realisieren, muss hier ein objekt-relationales Mapping durchgeführt werden. Dafür kamen bei uns 3 Möglichkeiten in Frage:

  1. Eine einzige Tabelle, die die Attribute aus allen anderen in sich vereint, ergänzt um ein Typfeld, in dem der Typ (Global, Kurs, User) festgehalten wird.
    • Vorteil: Sehr performant, da keine Joins bei einer Abfrage durchgeführt werden müssen.
    • Nachteil: Der Speicherplatz ist etwas erhöht, da bei jedem Datensatz nichtgenutzte Felder existieren. Da dieser aber so gering ist, wurde hier diese Möglichkeit gewählt.
  2. Eine Tabelle, die die gemeinsamen Attribute enthält und für jede abgeleitete Tabelle auch je eine eigene Tabelle, alle Tabellen ergänzt um einen Typ-Diskriminator.
  3. Je eine Tabelle für jede abgeleitete Tabelle.

Aktivieren und deaktivieren von Feeds:

  • Um einen Feed zu deaktivieren wird die feedId und die userId in die Tabelle feeds_deactivation geschrieben.
    • Hierbei bleibt der Feed-Eintrag in der Tabelle feeds_external_feed erhalten.
  • Soll ein deaktivierter Feed wieder aktiviert werden wird der Sperreintrag aus der Tabelle feeds_deactivation gelöscht

MySQL-CREATE-Script

DROP TABLE IF EXISTS `eStudyESS`.`feeds_deactivation`;
DROP TABLE IF EXISTS `eStudyESS`.`feeds_external_feed`;

CREATE TABLE `eStudyESS`.`feeds_external_feed` (
  `feedId`     int(11)       NOT NULL AUTO_INCREMENT,
  `feedName`   varchar(50)   NOT NULL,
  `feedSource` varchar(255)  NOT NULL,
  `creatorId`  int(11)       NOT NULL,
  `type`       char(1)       NOT NULL,
  `courseId`   int(11)       DEFAULT NULL,
  PRIMARY KEY (`feedId`),
  CONSTRAINT `feed_external_creatorid` FOREIGN KEY `feed_external_creatorid`
    (`creatorId`) REFERENCES `user` (`ID`) ON DELETE CASCADE ON UPDATE RESTRICT,
  CONSTRAINT `feed_external_coursid` FOREIGN KEY `feed_external_courseid`
    (`courseId`) REFERENCES `courses` (`ID`) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;

CREATE TABLE `eStudyESS`.`feeds_deactivation` (
  `feedId`     int(11)  NOT NULL,
  `userId`     int(11)  NOT NULL,
  PRIMARY KEY (`feedId`, `userId`),
  CONSTRAINT `feeds_deactivation_user` FOREIGN KEY `feeds_deactivation_userid`
    (`userId`) REFERENCES `user` (`ID`) ON DELETE CASCADE ON UPDATE RESTRICT,
  CONSTRAINT `feeds_deactivation_feed` FOREIGN KEY `feeds_deactivation_feedid`
    (`feedId`) REFERENCES `feeds_external_feed` (`feedId`) ON DELETE CASCADE ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;

Grafiken/ER-Diagramme

Interne Feeds

Als "Interne Feeds" werden von eStudy generierte Feeds bezeichnet, die ein Benutzer in seinem lokalen Feed-Reader abonnieren kann. Vorstellbar wären z.B. Feeds von Foren-Beiträgen oder der Literaturempfehlungs-Liste. Dieser Abschnitt beschreibt ein Konzept und dessen Realisierung.

Konzept

  • Feeds werden dynamisch von den jeweils zuständigen Modulen generiert.
  • Die dynamisch generierten Daten werden aus Performanzgründen gecacht. Für die Fragen "wann" und "wie" die Daten im Cache aktualisiert werden, haben wir 2 Möglichkeiten gefunden:
      1. Polling-basiert: Cron-Job, der alle 15 Minuten (oder so) läuft und dies durchführt
        • Vorteil: Nutzer erfährt im Gegensatz zur Event-basierten Realisierung mit großer Wahrscheinlichkeit keine Verzögerung der Ladezeit der Seite.
        • Nachteil: unnötige Last für den Server, wenn niemand online ist.
      2. Event-basiert: Bei Anfrage durch den User
        • Beispiel-Szenario:
        1. Benutzer fragt Feed um 10:00 Uhr ab. Die Feed-Daten werden gesammelt und in den Cache geschrieben.
        2. Benutzer fragt Feed um 10:10 Uhr ab. Er bekommt die gecacheten Daten gesendet.
        3. Benutzer fragt Feed um 10:20 Uhr ab (Die Zeit von 15 min ist also inzwischen überschritten). Die Feed-Daten werden erneut gesammelt und in den Cache geschrieben (die alten Daten im Cache werden verworfen).
        • Vorteil: keine unnötige Last für den Server, wenn dies nicht nötig ist
        • Nachteil: Derjenige Nutzer, der den Cache-Aktualisierungsprozess anstößt erfährt eine längere Wartezeit der Seite.

Realisierung

Die internen Feeds werden mittels des Zend-MVC-Frameworks realisiert.

Es existiert ein InternalFeedController, der eine Anfrage vom Client entgegen nimmt und die Verarbeitung an eine Modul-spezifische Feed-Implementierung weiterleitet. Außerdem empfängt der Controller das von der Modul-spezifischen Feed-Implementierung generierte Ergebnis und leitet dieses an die entsprechende Feed-View-Klasse weiter, welche eine RSS/Atom-XML-Datei an den anfragenden Client sendet. Es existiert ein InternalFeed-Interface und eine dazugehörige abstrakte Basisklasse (AbstractInternalFeed), die das Interface realisiert und Default-Implementierungen für die Methoden des Interfaces bereitstellt. Diese abstrakte Klasse ist die Basis für alle Modul-spezifischen Feed-Implementierungen.

Modul-spezifische Feeds

Wo eine Modul-spezifische Implementierung eines Feeds liegen muss und wie bzw. ob diese irgendwo registriert werden muss, wurde von uns diskutiert. Eine Teambesprechung ergab folgende Alternativen:

  1. Es existiert eine Datei, in der alle Pfade zu den Modul-spezifischen Feed-Implementierungen hinterlegt sind (hart-kodiert). Das heißt, jedes Mal, wenn ein Feed für ein neues Modul erstellt wird, muss diese Datei angefasst werden.
  2. Das gleiche wie bei Puntk 1., nur dass die Pfade anstatt in einer zentralen Datei in der Datenbank gespeichert werden. Dies vermeidet Code-Anpassungen. Es muss lediglich ein neuer Eintrag in der Datenbank erstellt werden.
  3. Die Modul-spezifischen Feed-Klassen folgen einer Konvention, die wie folgt aussehen kann: Modul-spezifische Feed-Klassen werden im Unterordner <modulname>/classes/class.feedimpl.inc.php abgelegt. So ist es nicht weiter nötig, diese Klasse irgendwo im System bekannt zu machen, da der InternalFeedController diese aufgrund der Namenskonvention findet. Außerdem ist festgelegt, dass die Klasse FeedImpl zu heißen hat und von der abstrakten Basisklasse AbstractInternalFeed ableiten bzw. das Interface InternalFeed direkt implementieren muss.

Der InternalFeedController

Datenbank-Design

MySQL-CREATE-Script

DROP TABLE IF EXISTS `eStudyESS`.`feed_internal`;

CREATE TABLE `eStudyESS`.`feed_internal` (
  `ModuleName` varchar(40) NOT NULL,      -- Name of the module
  `FeedName` varchar(80) NOT NULL,        -- Name of the feed
  `Path` varchar(255) NOT NULL,           -- Path to module specific feed class
  `Locked` tinyint(1) NOT NULL,           -- Determines if the feed is locked (1) or not (0)
  PRIMARY KEY (`ModuleName`, `FeedName`)
) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;

Grafiken/ER-Diagramme