Graph-IT

Knoten

Knoten sind das zentrale Konzept des Graphen. Alle Daten werden in Attributen und Verknüpfungen von Instanzen von Knoten gesammelt und verwaltet.

Da der Graph selbstbeschreibend ist, sind Knoten selbst Instanzen des Knotens knoten, der – zusammen mit einigen anderen in diesem Modul beschriebenen Knoten – in jedem Graphen vorhanden ist.

Knoten und Instanzen

Instanzen eines Knoten werden durch die API-Funktion erzeuge(string $knoten_typ): ?string angelegt, die den Typ/Namen des Knoten als einzigen Parameter bekommt und, falls dieser existiert, die GUID der neu angelegten Instanz zurück gibt.

GUIDs sind in der momentanen Implementierung zufällige 16-Byte-Werte (dargestellt als Strings von 32 hexadezimalen Ziffern), wobei die ersten 4 Byte bzw. 8 Hex-Ziffern eine Knoten-ID sind, die für alle Instanzen eines Knoten gleich ist.

Die entgegengesetzte API-Funktion ist vernichte(string $node_guid): ?boolean, die eine Instanz löscht. Diese darf aber nur in der …_loeschen-Aktionfunktion des Knoten verwendet werden, die weiter unten beschrieben wird.

Die API-Funktion knotentyp(string $node_guid): ?string gibt den Knoten-Typ zu einer GUID zurück. Dies wird durch die enthaltene Knoten-ID erheblich erleichtert, da nur der Knoten-Typ zu dieser ID ermittelt werden muss.

Daten-Knoten und Task-Knoten

Durch das Graphmodul prozesse wird eine konzeptionelle Unterscheidung zwischen Daten- und Task-Knoten eingeführt. Daten-Knoten enthalten die tatsächlichen Anwendungs-Daten. Ihre Instanzen werden aber von den Benutzern des Systems nicht direkt manipuliert, sondern nur mittels Instanzen von Task-Knoten.

Die Verwaltung von Berechtigungen, das Logging von Zeitpunkten, wann eine Änderung von wem vorgenommen wurde, und das Prüfen von Bedingungen für diese Änderungen kann dann komplett in den Task-Instanzen vorgenommen werden, während die Daten-Instanzen relativ schlank bleiben.

Die grundlegende Graph-API ist von dieser konzeptionellen Unterscheidung nicht direkt betroffen, da sowohl Daten- als auch Task-Knoten und -Instanzen auf den gleichen Basis-Strukturen von Knoten, Attributen, Verknüpfungen, usw. implementiert sind.

Primär-Attribute und Namen

Für jeden Knoten ist ein eindeutiges Attribut als Primär-Attribut ausgezeichnet. Dieses muss als erstes Attribut jeder Instanz initialisiert werden und darf sich über die Lebenszeit der Instanz nicht ändern. Es ist dazu gedacht, in allen Fällen verwendet zu werden, in denen eine Instanz gesucht werden muss, um auf sie zuzugreifen. Insbesondere kann dies auch bei der Kommunikation über mehrere Graphen hinweg verwendet werden, da die (zufälligen) GUIDs sich zwischen verschiedenen Graphen eventuell unterscheiden.

Eine einfache Möglichkeit, Primär-Attribute zu vergeben, sind fortlaufende Nummern, beispielsweise für Kunden oder Rechnungen. Für untergeordnete Knoten – beispielsweise Rechnungs-Positionen – können diese dann um einen weiteren Zähler erweitert werden.

Ein Knoten kann auch noch andere eindeutige – und als solche festgelegte – Attribute besitzen. Die API-Funktion attributsknoten(string $attributknoten_typ, string $wert): ?string kann verwendet werden, um die GUID einer Instanz für den Wert eines solchen eindeutigen Attributs zu finden.

Jeder Knoten besitzt außerdem ein Attribut name, das ebenfalls eindeutig ist. Dies kann das Primär-Attribut sein, muss es aber nicht. Der Name ist dazu gedacht, eine Instanz in einer menschenlesbaren Form darzustellen.

Hierfür kann es sinnvoll oder sogar notwendig sein, dass sich der Name während der Lebenszeit der Instanz durch Korrekturen, Umbenennungen, neue Richtlinien für Namen, … ändert. In diesem Fall darf der Name dann nicht mehr selbst das Primär-Attribut sein. Er kann aber das Primär-Attribut enthalten, um auf einfache Art und Weise die Eindeutigkeit sicherzustellen.

Für Kunden kann das Attribut name beispielsweise zusätzlich zur Kundennummer den Namen und den Ort enthalten, um es Menschen einfacher zu machen, diese Instanz zu identifizieren. Für Rechnungen könnte zusätzlich das Datum und der Kunde im Namen enthalten sein und für Rechnungs-Positionen der Artikel, auf den sich die Position bezieht.

Gültigkeit von Instanzen

Jeder Knoten besitzt ein Attribut ungueltig, welcher durch eine Datenfunktion gesetzt wird, die überprüft, ob Instanzen des Knoten konsistent sind, ob sie alle Invarianten erfüllen, die für diesen Knoten unbedingt und immer gelten sollen.

Beispiele für solche Invarianten wären, dass ein Kunde immer eine Rechnungs-Anschrift haben muss, dass eine Rechnung immer einem Kunden zugeordnet sein muss, oder, dass eine Rechnungs-Position immer zu einer Rechnung gehören muss. Einige Invarianten können natürlich auch Design-Entscheidungen sein. Es könnte festgelegt sein, dass eine Rechnung immer mindestens eine Position enthalten muss, das System könnte aber auch so entwickelt werden, dass während des Erstellens der Rechnung Situationen erlaubt sind, in denen noch keine Position vorhanden ist.

Task-Knoten und andere Funktionalitäten, die Instanzen anlegen oder ändern, müssen stets sicherstellen, dass alle Instanzen diese Invarianten/Bedingungen erfüllen und gültig sind.

Die Existenz von ungültigen Instanzen ist also ein Hinweis auf einen Implementierungs-, keinen Benutzer-Fehler. Fehler durch die Benutzer müssen vorher bereits – durch Tasks, spezialisierte Oberflächen, … – abgefangen werden und dürfen nicht zu ungültigen Instanzen führen.

Löschbarkeit und Löschen von Instanzen

Jeder Knoten besitzt ein Attribut loeschbar, das durch eine Datenfunktion gesetzt wird und eine Aktionfunktion loeschen, um Instanzen dieses Knoten aus dem Graphen zu entfernen. Die Aktionfunktion loeschen überprüft immer, ob das Attribut loeschbar gesetzt ist und bricht anderenfalls ab. Sie ist die einzige Art von Funktion, die die API-Funktion vernichte() aufrufen darf.

Durch loeschbar kann beispielsweise festgelegt werden, dass Kunden nur löschbar sind, wenn keine Rechnungen an sie im Graphen enthalten sind, da die Instanz sonst noch für die Konsistenz des Graphen insgesamt benötigt wird.

Wenn eine Instanz löschbar ist, muss loeschen sicherstellen, dass keine ungültigen Instanzen zurück bleiben. Dies kann dadurch erreicht werden, dass untergeordnete Strukturen – beispielsweise die Rechnungs-Positionen einer Rechnung – mit gelöscht werden.

In vielen Fällen ist es eine Design-Entscheidung, ob verknüpfte Strukturen in loeschbar aufgenommen werden und das Löschen verhindern, bis sie von Hand oder durch andere Funktionen entfernt werden, oder, ob sie durch loeschen automatisch mit entfernt werden. Die Implementierungen von loeschbar und loeschen müssen also aufeinander und auf diese Entscheidungen abgestimmt werden.