Die folgende Diskussion war der Weg, der zur Projektstruktur geführt hat. Sie kann übersprungen werden, und das Ergebnis der Suche kann hier gesehen werden.
Die Projektstruktur in PRO•M ist hierarchisch:
Wenn wir DDD1) anwenden, müssen wir innerhalb dieser Struktur Aggregates identifizieren und definieren. In der Delphi 6.0 Entwicklung war das einzelne Projekt das Aggregate, und für bestimmte Änderungen wurde UOW2) verwendet, indem mehrere Projekte gemeinsam in eine Transaktion behandelt wurden. Wird z.B. Projekt 1.1.2 von Projekt 1.1 entfernt und unter Projekt 1.2 gehängt, werden alle 3 Projekte gesperrt. Es wird dann geprüft, ob Projekt 1.1.2 kein Vorfahr von Projekt 1.2 ist, weil es ansonsten einen Zirkel gibt3).
Es wird aber auch geprüft, ob Projekt 1.2 noch keine Vorgänge unter sich hängen hat. Dafür werden dann die Projekte 1.2.1. und 1.2.2 auch noch gesperrt4).
Wenn also Projekt 1.1.2 unter Projekt 1.2 gehängt wird, werden folgende Projekte gesperrt:
Das bedingt eine Transaktion, bzw. ein UOW5). Dafür müssen alle Projekt auch in einem Dataspace leben, ansonsten müsste man eine verteilte Transaktion für den UOW verwenden, etwas das spätestens seit Pat Hellands Artikel vermieden werden sollte.
Das System könnte für die Projektstruktur verantwortlich sein: Vorteile:
Hierbei handelt es sich aber um ein globales Aggregate mit allen bekannten Nachteilen:
Man könnte das Hauptprojekt, bzw. den Projektstrukturplan7) als Aggregate Root verwenden: So können alle Regeln sicher gestellt werden, u.a.
Es gibt trotzdem noch Problemen mit dieser Struktur, ähnlich wie bei einem globalen Aggregate:
Ein Problem ist, dass die Projektstruktur Projekte enthält. Im Grunde müsste die Projektstruktur Referenzen auf Projekt enthalten, wobei jedes Projekt ein eigenes Aggregate ist:
Das sieht ja schon gewaltig aus. Ist aber im Grunde recht einfach. Ein Projekt ist ein eigenständiges Aggregate, und kann auch als solches existieren. Über die Projektstrukturpläne wird es in eine Struktur eingefügt.
Innerhalb einer Projektstruktur können Regeln bezüglich der Zirkelbezüge und dem Vermischen von Projekten und Projektvorgängen geprüft und eingehalten werden. Das Arbeiten am Projekt ist unabhängig vom Arbeiten an der Struktur, was schon ein großer Schritt in die richtige Richtung ist.
Was noch nicht geht ist, dass die Teilprojektleiter unabhängig von einander die Strukturen bestimmen können. Ein weiters Problem entsteht, wenn man z.B. die Referenz auf Projekt 1.2 aus der Projektstruktur 1 in die Projektstruktur 2 unter die Referenz auf Projekt 2 hängen möchte12). Man müsste alle Referenzen unter der Referenz auf Projekt 1.2 ebenfalls mit rüber nehmen, also die Referenzen auf die Projekte 1.2.1 und 1.2.2. Es gäbe also 6 Schritte:
Es sind 6 Schritte, die 2 Aggregates betreffen. Die Reihenfolge muss eingehalten werden, weil die Struktur nach jedem Schritt konsistent sein muss. Man könnte die Schritte 1 bis 3 und 4 bis 6 verschmelzen. Es bleiben aber immer auf jeden Fall 2 Schritte.
Das bedeutet auch, dass die globale Sicht nicht immer zwingend konsistent sein muss. Wenn man das Ergebnis der Schritte 4 bis 6 vor dem Ergebnis der Schritte 1 bis 3 sieht13), würde man Projekt 1.2 mit den Teilprojekten 1.2.1 und 1.2.2 in beiden Projektstrukturen sehen. Das wäre nur vorübergehend, und man würde Mechanismen einbauen, um die Mengen-basierte Regel schließlich konsistent einzuhalten14).
Genauso wichtig, wenn nicht wichtiger, ist es, dass die Komponenten, die das Lesemodell verwenden, von dieser möglichen, vorübergehenden Inkonsistenz wissen und sie nicht als Fehler sehen, sondern Wege haben, damit umzugehen. Es gibt aber auch noch folgende Möglichkeit:
Was hat das denn wieder auf sich? Es gibt in diesem Modell keine separaten Projektstrukturen, die vorhanden Projekte in eine Baumstruktur bringen. Die Struktur ist Teil des Projektes. Das ist im Grunde eine sehr natürliche Herangehensweise. Ein Organisationsleiter kann ein Hauptprojekt anlegen, es auf mehrere Teilprojekte unterteilen, und diese abgeben. Er bestimmt die Teilprojektstruktur unter dem Hauptprojekt. Das Teilprojekt kann dann aber komplett an jemanden anderes abgegeben werden15).
Die Teilprojekte sind völlig unabhängig von einander. Ein Projekt kümmert sich selbst um die Regel, dass man Projekte und Projektvorgänge16) nicht auf einer Ebene mischen darf.
Wenn man nichts ändern darf, ist eine Baumstruktur ohne Zirkel sicher gestellt. Wie ein Baum wächst die Struktur, beginnend mit einem Hauptprojekt, aus dem Teilprojekte entwachsen. Aus jedem Teilprojekt können wieder neue Äste wachsen, usw.
Das umgedrehte Modell ist eins, in dem ein Projekt nicht seine Teilprojekte kennt, sondern ein Teilprojekt sein übergeordnetes Projekt kennt: Datenbank-technisch baut man so eine Baumstruktur auf. Die Herangehensweise scheint auch recht natürlich: Als Verantwortlicher eines Projektes lege ich ein Teilprojekt an, das auf mich zeigt. Ab dann wird es aber unnatürlich. Ich weiß nämlich nichts mehr vom Teilprojekt sobald die Aktion fertig ist. In dem Modell von oben weiß das Teilprojekt nichts vom übergeordneten Projekt - was ist also besser? Gucken wir uns mal eine Regel an:
Wenn das Teilprojekt sein übergeordnetes Projekt kennt, kann diese Regel nicht strikt eingehalten werden. Vor der Teilprojektanlage wird geprüft, ob das übergeordnete Projekt bereits Projektvorgänge hat. Wenn ja, wird die Anlage abgelehnt, wenn nicht, kann das Teilprojekt angelegt werden. Diese Prüfung läuft aber außerhalb der Transaktion, mit der Möglichkeit, dass das Ergebnis hinfällig ist sobald das Teilprojekt angelegt ist. Man müsste somit den Fall einer Vermischung mit einarbeiten und dafür kompensieren.
Wenn ein Projekt seine Teilelemente kennt, und separate Referenzen auf Teilprojekte und auf Projektvorgänge hält, dann kann mit absoluter Sicherheit verhindert werden, dass Teilprojekte und Projektvorgänge auf einer Ebene vermischt werden.
Diesen Vergleich gewinnt also die Struktur, in der das Projekt seine Teilprojekte kennt vs. der Struktur in der das Teilprojekt sein übergeordnetes Projekt kennt.
Das ist ein inhärenter Teil der Definition einer Baumstruktur.
Ein Teilprojekt kennt sein übergeordnetes Projekt, und ein Projekt kennt seine Teilprojekte: Wie kann das funktionieren? Ist da nicht unendlich viel Potenzial für Konflikte? Gehen wir mal die einzelnen Fälle durch:
Eine Teilprojektanlage betrifft immer 2 Aggregates. Es wird ein neues Projekt hinzugefügt, welches auf das übergeordnete Projekt zeigt, und dem übergeordneten Projekt wird das neue Teilprojekt hinzugefügt. Man muss in diesem Fall nur die Reihenfolge einhalten, erst das Teilprojekt anzulegen, dann das Teilprojekt der Liste der Teilprojekte des übergeordneten Projektes hinzuzufügen. Der zweite Schritt darf erst angefordert werden, nachdem der erste erfolgreich durchgeführt wurde. Das Read Model benötigt immer beide Schritte, um das neue Teilprojekt als solches zu akzeptieren. Nur wenn die Ereignisse beider Schritte beobachtet/erhalten wurden, wird das neue Teilprojekt angezeigt. Ein Projekt mit Referenz zu einem übergeordneten Projekt, das aber nicht in der Liste der Teilprojekte dieses Projektes enthalten ist, existiert im Grunde nicht.
Das ist schwieriger, und hierfür muss ein Prozess herhalten. Wenn man bedenkt, dass das Read Modell ein Teilprojekt einem übergeordneten Projekt immer nur zuordnet, wenn es von beiden Projekten die Information erhält, dass die Zusammenführung passiert ist, wäre folgender Prozess denkbar, wenn Teilprojekt 1.1 von Projekt 1 unter Projekt 2 gehängt werden soll:
Diese Schritte, in dieser Reihenfolge, sind notwendig und ausreichend. Im Lesemodell wird Teilprojekt 1.1 immer unter höchstens18) einem Projekt hängen. Es wird niemals verloren gehen, was z.B. der Fall wäre, wenn es erst aus der Teilprojektliste von Projekt 1 entfernt würde, und der Prozess dann hängen bleibt.
Wenn nun Schritt 1 erfolgreich durchgelaufen ist, Schritt 2 aber abgelehnt wird, würde die Saga kompensieren, indem sie das Teilprojekt wieder aus der Teilprojektliste von Projekt 2 entfernt. Dann ist alles beim Alten. Wenn Schritte 1 und 2 durchlaufen, Schritt 3 aber fehlschlägt, gibt es einen Zustand, der aufzulösen ist. Es sollte keinen Grund geben, dass der letzte Schritt fehlgeschlagen ist, da man vor dem ersten Schritt bereits geprüft haben sollte, ob Projekt 1 ein Entfernen von Teilprojekt 1.1 erlaubt. Im besten Falle merkt man nichts davon, da das Lesemodell Projekt 1.1 nicht mehr in den Teilprojekten von Projekt 1 anzeigt.
Im schlimmsten Fall kann man Projekt 1 aber keinen Vorgang hinzufügen, weil es denkt, dass es noch ein Teilprojekt besitzt. Hier liegt aber die Krux von kompensierenden Aktionen. Es gibt keine transaktionale Sicherheit. Angenommen, Teilprojekt 1.1 wird gleichzeitig unter Projekt 3 gehängt, und folgendes passiert:
Jetzt hängt Teilprojekt 1.1 unter Projekt 3, weil sie sich gegenseitig referenzieren. Teilprojekt 1.1 ist aber auch in der Liste der Teilprojekte von Projekt 2. Kann man es dort einfach wieder löschen? Das ist gefährlich, denn angenommen es handelt sich um den ersten Fall, in dem das Teilprojekt unter Projekt 2 gehängt wird19), und es ist gerade der erste Schritt durchlaufen worden, die nächsten beiden stehen noch an. Wenn man nun Teilprojekt 1.1 händisch aus der Teilprojektliste von Projekt 2 löscht20), würde der Prozess in den nächsten beiden Schritten Projekt 2 als übergeordnetes Projekt von Teilprojekt 1.1 setzen und Teilprojekt 1.1 aus der Teilprojektliste von Projekt 1 entfernen. Das Ergebnis wäre, dass Teilprojekt 1.1 zwar Projekt 2 referenziert, aber in keinem der beiden Projekte21) in der Liste der Teilprojekte enthalten ist.
Der Prozess des Umhängens muss also erweitert werden, damit er keinen inkonsistenten Zustand hinterlässt. Und zwar am besten so, dass keine verteilte Transaktion notwendig wird, und auch keine Reservierungen. Erstens wird es die Möglichkeit geben, ein Teilprojekt einem Projekt als schwebend hinzuzufügen. Das verbessert die Teilprojektanlage:
In dieser Version wird dem übergeordneten Projekt als erstes ein schwebendes Teilprojekt hinzugefügt. Dadurch weiß man, dass das Teilprojekt vom Projekt akzeptiert wird, und das Projekt kann sich auf ein Teilprojekt vorbereiten. Jetzt wird das neue Teilprojekt hinzugefügt, welches auf das übergeordnete Projekt zeigt - und zwar in dem Wissen, dass das übergeordnete Projekt es akzeptieren wird, und es nicht u.U. brach rumliegen wird. Danach wird dem übergeordneten Projekt mitgeteilt, dass aus dem schwebenden Teilprojekt ein echtes werden kann. Es ist im Lesemodell aber bereits als schwebendes Teilprojekt sichtbar - der Grund liegt im Umhängen.
Es wird für ein sicheres Umhängen zudem im Projekt möglich sein, ein Teilprojekt zum Löschen vorzumerken:
Teilprojekt 1.1 wird mit folgendem Prozess von Projekt 1 unter Projekt 2 gehängt:
Schritte 1 und 2 garantieren, dass Schritte 4 und 5 funktionieren, bzw. können Schritte 1 und 2 garantiert rückgängig gemacht werden, wenn Schritt 3 fehlschlägt. Schritt 3 muss selbst nicht mit einer Vormerkung durchgeführt werden, weil alle folgenden Schritte durch die vorhergehenden garantiert funktionieren werden.
Der gesamte Prozess wird von den Schritten 1 und 5 ummantelt, d.h. dass man Teilprojekt 1.1 nicht mehrmals gleichzeitig umhängen kann - der Prozess wird vom übergeordneten Projekt geschützt. Wenn ein Umhängen also nur über diesen Prozess laufen kann, wird Teilprojekt 1.1 immer maximal unter einem Projekt hängen, und immer schließlich unter genau einem übergeordneten Projekt.
Dieser Prozess läuft genau so ab wie der Prozess des Umhängens, nur dass es die Schritte 2 und 4 nicht gibt.
Hierfür reicht der Prozess noch nicht aus. Wenn man Projekt 1 gleichzeitig unter Projekt 2 und unter Projekt 3 hängt, und dafür die Schritte 1, 3 und 5 des Prozesses umsetzt, würden die Schritte alle erfolgreich durchlaufen, und entweder Projekt 2 oder Projekt 3 hätten Projekt 1 in der Teilprojektliste, ohne dass es zurück auf das Projekt zeigt. Somit muss der gesamte Prozess durch ein Vormerken im Teilprojekt geschützt werden, dass es umgehängt werden soll. Damit das auch unter allen Umständen mit ein hundert prozentiger Sicherheit funktioniert, müssen alle Aktionen darüber geschützt werden. Also sieht es letztendlich so aus:
In dieser Version wird dem übergeordneten Projekt als erstes ein schwebendes Teilprojekt hinzugefügt24). Dadurch weiß man, dass das Teilprojekt vom Projekt akzeptiert wird, und das Projekt kann sich auf ein Teilprojekt vorbereiten. Jetzt wird das neue Teilprojekt hinzugefügt, welches unter Vorbehalt auf das übergeordnete Projekt zeigt - und zwar in dem Wissen, dass das übergeordnete Projekt es akzeptieren wird, und es nicht u.U. brach rumliegen wird. Danach wird dem übergeordneten Projekt mitgeteilt, dass aus dem schwebenden Teilprojekt ein echtes werden kann. Es ist im Lesemodell noch nicht als Teilprojekt des Projektes sichtbar. Das wird es erst nach dem letzten Schritt, in dem es fest auf das übergeordnete Projekt zeigt.
Teilprojekt 1.1 wird mit folgendem Prozess von Projekt 1 unter Projekt 2 gehängt:
Der Prozess wird nun durch Teilprojekt 1.1 geschützt, was auch Sinn hat, denn ein gleichzeitiges Umhängen eben dieses Teilprojektes soll ja verhindert werden. Wie man erkennt, handelt es sich beim Umhängen auch um einen Prozess und nicht um einen einfachen Befehl an ein Aggregate, weil der Prozess mehrere Aggregates betrifft. Aber:
Warum setzt man nicht einfach im Teilprojekt das übergeordnete Projekt? Ein Befehl, ganz simpel. Es gibt dafür mehrere Gründe:
Zirkelbezüge in der Projektstruktur können auch mit bidirektionalen Beziehungen nicht verhindert werden. Dazu müsste man die Projektstruktur wie in einigen der Modelle weiter oben verwenden. Entweder die System-weite, globale Projektstruktur, oder aber Projektstrukturen pro Hauptprojekt. Das führt aber zu ganz anderen Problemen, vor allem dazu, dass wir zwar Konsistenz haben, aber keine Teilbarkeit des Systems, bzw. keine Verfügbarkeit31).
Hier sind die Regeln, die eingehalten werden müssen, und die zur letztendlichen Struktur geführt haben:
Die ersten 3 Regeln sagen nichts anderes, als dass es eine globale Projektstruktur geben muss. Nur so kann man diese Regeln konsistent einhalten. Da eine Projektanlage aber ein Punkt ist, bei dem die Konsistenz absolut vorrangig ist, wird der Anwender verstehen, wenn es dort eventuell zu Zeitverzögerungen kommen kann32).
Die letzten beiden Punkte besagen, dass Änderungen innerhalb eines Projektes aber nicht global serialisiert werden dürfen. Sobald ein Projekt angelegt wurde, sollten Änderungen maximal innerhalb des Projektes serialisiert werden. Die globale Projektstruktur bestimmt also nur die Position des Projektes im Baum. Jedes Projekt hat dann wiederum eine eigene Projektstruktur für Produkte, Arbeitspakete und Aufgaben|Vorgänge.
Die mehrstufige Projektstruktur löst das Problem der gleichzeitigen Zugriffe auf unterschiedliche Teilprojekte. Da die Projekt von der Baumstruktur lediglich referenziert werden33), hängen sie nicht alle gemeinsam im Projektbaum Aggregate und kommen sich nicht in die Quere.
Dass der Projektbaum für eine Neuanlage eines Projektes/Teilprojektes komplett serialisiert werden muss fühlt sich noch unschön an. Was für eine schreckliche Aussage! Wenn aber Konsistenz der Schwerpunkt ist, und zwar sofortige, transaktionale Konsistenz, und keine schließliche Konsistenz, muss das so sein. Was man zumindest optimieren kann, ist nicht immer den gesamten Baum laden zu müssen. Wenn z.B. ein neues Projekt angelegt wird, braucht man den Baum gar nicht zu laden, denn durch ein neues Projekt kann es nicht zu Zirkelbezügen kommen. Man muss nur das Projekt dem Baum hinzufügen. ORM34) Tools wie EF35) bieten das mit lazy loading an.
Es ist ja nicht in Stein gemeißelt, und vielleicht kann man die globale Projektstruktur später noch aufteilen. Wenn man z.B. die Regel aufheben kann, dass ein Teilprojekt zum Hauptprojekt werden kann, oder ein Hauptprojekt zum Teilprojekt, dann hat man keinen globalen Projektbaum mehr, sondern einzelne Hauptprojektbäume. Wenn die Projekte in solch einem Hauptprojektbaum diesen nicht verlassen können, braucht man kein globales Aggregate, weil die Hauptprojektbäume vollkommen unabhängig voneinander existieren können.
Die Projektneuanlage wird so ablaufen, dass erst ein neues Projekt angelegt wird, und es danach im Baum an der richtigen Stelle eingesetzt wird. Das Lesemodell wird das Projekt erst anzeigen, wenn beide Aktionen erfolgreich ausgeführt wurden. Ein neues Projekt in den Baum zu hängen sollte immer funktionieren, daher sollte es selten dazu kommen, dass Projekte brach rum liegen. Und auch wenn, haben sie noch keine Projektnummer erhalten, und sind sonst auch nirgendwo verwendet worden36), stören also nicht weiter.
Ein Projekt wird im Projektbaum umgehängt. Das ist ein Vorgang, der einen Schritt benötigt, somit immer konsistent durchgeführt wird. Wenn es bloß so einfach wäre. Es gibt ja noch die Regel auf einer Ebene dürfen Projekte, Produkte, Teilaufgaben, Arbeitspakete, Aktivitäten und Vorgänge nicht vermischt werden. Das Projekt als logische
Es gibt Projekte mit Teilprojekten37). Ein Hauptprojekt ist Teil seiner eigenen Teilprojektliste - ein Projekt muss immer Teil mindestens einer Teilprojektliste sein, zum Schutz der gleichzeitigen Verschiebung des Projektes durch mehrerer User:
Beide Schritte müssen ausgeführt worden sein, damit das Projekt im Lesemodell erscheint. Zudem wird der zweite Schritt immer nur nach dem ersten ausgeführt werden können, da das Hauptprojekt für den zweiten Schritt ja das Aggregate Root ist.
Wie in der Hauptprojektanlage müssen alle Schritte durchgeführt werden bevor das neue Projekt im Lesemodell erscheint.
Das Umhängen des Projektes wird durch das übergeordnete Projekt geschützt, d.h. mann wird es nicht gleichzeitig mehrmals verschieben können. Der erste Schritt schlägt fehl, wenn das Projekt bereits von einem anderen Prozess zum Löschen markiert wurde. Daher auch der Umstand, dass ein Hauptprojekt unter sich selbst hängt, denn ansonsten könnte man es nicht schützen.
Wie man sieht, hat ein Projekt selbst gar keine Wahl ob es umgehängt wird oder nicht. Man kann in den Prozess einbauen, dass es eine Referenz auf das Hauptprojekt hält, und in Schritt 3 umgehängt wird, nachdem das alte und das neue übergeordnete Projekt ihr Einverständnis erklärt haben.
Davon wird aber abgesehen, denn es wird von einem Top-Down Workflow ausgegangen. Der Verantwortliche des übergeordneten Projektes entscheidet, was mit den Teilprojekten passiert, bzw. wo sie zu liegen haben. Der Verantwortliche des Teilprojektes kümmert sich nur um den Inhalt des Teilprojektes, also um weitere Teilprojekte, Produkte, Teilaufgaben, Arbeitspakete, usw.
Es kann passieren, dass Projekt 1 in der Teilprojektliste von Projekt 2 ist, und umgekehrt. Was wären die Ereignisse?
Schritt 4 kann sogar vor Schritt 3 passieren, bzw. vom Lesemodell beobachtet werden. Es würde 2 Tabellen im Lesemodell geben. Einmal die Tabelle mit den Projektdetails, dann die Tabelle mit der Projektstruktur:
Project-Id | Details |
---|---|
Project-Id | Parent-Project-Id |
---|---|
Wie sehen die Tabellen nach den einzelnen Schritten aus?
Project-Id | Details |
---|---|
Projekt 1 | … |
Project-Id | Parent-Project-Id |
---|---|
Project-Id | Details |
---|---|
Projekt 1 | … |
Project-Id | Parent-Project-Id |
---|---|
Projekt 1 | NULL |
Project-Id | Details |
---|---|
Projekt 1 | … |
Projekt 2 | … |
Project-Id | Parent-Project-Id |
---|---|
Projekt 1 | NULL |
Project-Id | Details |
---|---|
Projekt 1 | … |
Projekt 2 | … |
Project-Id | Parent-Project-Id |
---|---|
Projekt 1 | NULL |
Projekt 2 | Projekt 1 |
Project-Id | Details |
---|---|
Projekt 1 | … |
Projekt 2 | … |
Project-Id | Parent-Project-Id |
---|---|
Projekt 1 | Projekt 2 |
Projekt 2 | Projekt 1 |
…OK, das reicht, also hier ist die endgültige Entscheidung…