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.
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.
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.
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.
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.
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.