Developer Guide

Allgemeines

Der Developer Guide ist die Basis für eine möglichst einheitliche technische Basis beim Aufbau von Applikationen.

Namensraum für Applikationen

Der Namensraum einer Applikation sollte in der Benennung der Datengruppen, Request-Parameter und Session-Parameter verwendet werden. Der Namensraum kann z.B. auch ein Firmenname oder eine Kombination aus Firma und Applikation sein. Hier liegt die größte Herausforderung, denn bei einer Vielzahl von Partnern und Applikationen kann es durchaus zu Überschneidungen bei der Namensgebung kommen.

Im Handbuch zur Applikation sollten mindestens die verwendeten Request-Parameter und deren mögliche Werte aufgelistet werden, wenn die Applikation im Portal mit Requestwert-Validatoren abgesichert werden soll.

Datengruppen

Intrexx-Datengruppen erhalten bei der Erstellung je nach Typ automatisiert Namen um doppelte Namensgebungen zu vermeiden. Für die einfache Erkennbarkeit in SQL-Anweisungen (z.B. in Groovy oder Velocity) sollten alle Tabellennamen entsprechend angepasst werden. Folgende Regeln sollten bei der Benennung der Datengruppen grundsätzlich eingehalten werden:

  1. maximale Länge des Tabellennamens: 26 Zeichen

  2. möglichst sprechender Name (z.B. SHARE_FEED)

  3. Einzahl

  4. Englisch

  5. Großschreibung

Intrexx-Datengruppen

Intrexx-Datengruppen erhalten bei der Erstellung automatisch den Namen "XDATAGROUP" gefolgt von einem Zufallsschlüssel, um doppelte Namensgebungen zu vermeiden.

Datei-Datengruppen

Bei Erzeugung eines Datenfeldes mit dem Datentyp "Datei" wird auch automatisch eine untergeordnete Datengruppe erstellt. Diese Datengruppe wird dabei mit dem Namen "XFILEDATAGROUP" betitelt, gefolgt von einem Zufallsschlüssel. Benennen Sie die Datei-Datengruppen mit derselben Logik, mit der auch die regulären Datengruppen benannt werden.

Systemdatengruppen

Die Benennung von Systemdatengruppen sollte analog zu den Intrexx-Datengruppen erfolgen. Der Name sollte immer mit "SETTING" enden.

Fremddatengruppen

Bei Fremddatengruppen mit Verbindung zu Intrexx-Standard-Datengruppen (z.B. DSUSER, DSOBJECT, etc.) oder auch auf die Intrexx-Datengruppen in der Applikation ist keine Umbenennung erforderlich.

Bitte achten Sie darauf, dass keine datenbankspezifischen Präfixe in der Datengruppendefinition stehen (z.B. dbo.<Datengruppenname>), sondern nur der reine Datengruppenname in Großbuchstaben.

Datenfelder

Die passende Benennung von Datenfeldern ist ebenfalls wichtig, wenn Applikationen später modifiziert bzw. erweitert werden und per SQL-Statements in Velocity oder Groovy Zugriffe erfolgen. Die Lesbarkeit und auch die internationale Verwendung der Applikation spielen hier eine Rolle.

Feldnamen

Die Namen von Intrexx-Datenfeldern bestehen aus einem Typschlüssel gefolgt vom dem Titel, der bei der Erstellung des Datenfeldes bzw. des jeweiligen Elements erfasst wurde (z.B. "STR_TITEL_6A585D30"). Dabei werden Umlaute umgewandelt und der Feldname, falls notwendig, auf 30 Zeichen gekürzt. Um die Verwendung von SQL-Schlüsselwörtern zu vermeiden und eine optimale Lesbarkeit zu erreichen, sollten Feldnamen wie folgt formuliert werden:

  1. maximale Länge des Spaltennamens 30 Zeichen

  2. möglichst sprechender Name

  3. Präfix mit Typkürzel des Datenfeldtyps, gefolgt von Unterstrich (z.B. STR_NAME, B_IS_VISIBLE, etc.)

  4. Englisch

  5. Großschreibung

Hier eine Übersicht über Präfixe:

  • String: STR

  • Boolean: B

  • Float und Currency: FLT

  • Longtext: TXT

  • Datum, Uhrzeit, Datum und Uhrzeit: DT

  • Datei: FILE

Primärschlüssel (Primary Key)

Der Datentyp des Primärschlüssels kann ein Integer-Wert oder eine GUID sein. Die GUID hat gegenüber einer Ganzzahl mehrere Vorteile:

  • Sicher gegen das Erraten der Datensatz-ID - Integer-Werte können durch Hochzählen erraten werden.

  • Ermittlung der neuen ID bei Anlage eines neuen Datensatzes nicht erforderlich.

  • Lückenlose ID - beim Löschen von Datensätzen entstehen bei Ganzzahlwerten Lücken.

  • Beim Zusammenführen oder Kopieren von Daten mit GUIDs müssen keine IDs ermittelt werden. Der Aufwand und die Fehleranfälligkeit sind bei GUIDs geringer als bei Integer-Werten. Der notwendige Code ist schlanker.

Referenzen

Die Namen von Referenz-Datenfeldern haben in Intrexx automatisch das Präfix "REF_" gefolgt von einem Zufallsschlüssel. Über die Eigenschaften des Referenz-Datenfeldes kann der Name vor dem ersten Veröffentlichen der Applikation auf dem Expert-Reiter angepasst werden. Blenden Sie dazu die Datenfelder über das Hauptmenü Bearbeiten / Datenfelder anzeigen ein, wenn die Datengruppe in der Applikationsstruktur ausgewählt ist.

Index

Zur Performance-Optimierung sollten für Datenfelder, die besonders häufig durchsucht oder für die Sortierung verwendet werden, Indexe aufgenommen werden. Der Indexname darf eine Länge von 18 Zeichen nicht überschreiten und wird nach folgendem Namensschema aufgebaut: IX_<DG-Kürzel>_<ZÄHLER>, z.B. IX_SHAREFEED_1.

Benennung von Elementen

Um möglichst einheitliche Benennungen im Portal zu erhalten, sollten einige Regeln beachtet werden. Grundsätzlich gilt jedoch, möglichst alle Elemente mit entsprechenden Namen zu benennen und nicht die Standard-Vorgabe beizubehalten.

Ansichtsseiten

Die Titel von Seiten werden im Browser z.B. als Tooltip-Titel oder in Pfadangaben auf die Seite verwendet. Daher können die Bezeichnungen für die Entwicklung nicht logisch getrennt von der Anzeige vergeben werden. Ausnahmen bilden jedoch einige einsatzabhängige Ansichtsseiten:

Gruppierungen

Da Gruppierungen häufig auch per JavaScript ein- und ausgeblendet werden, sollten diese sprechende Namen nach folgendem Schema erhalten: _grpGruppenname. Der Gruppenname sollte in Englisch definiert sein.

Benennungsregeln

Die INTREXX GmbH Qualitätssicherung führt automatisierte Tests von Applikationen durch. Dabei werden die Applikationsstruktur, Programmcode (JavaScript, Groovy, Velocity) und Layout auf Einhaltung bestimmter Regeln geprüft. Dazu gehören auch die folgenden Benennungen:

Testart

Test

Bemerkung

Benennung

E-Mail

Die Benennung ohne Trennstrich wie eMail, email, Email sind nicht zulässig. Dies betrifft jedoch nur die Beschriftungen – nicht die Datenfelder.

Tabellenlänge

Die Einhaltung der maximalen Länge von Tabellennamen

Tabellenspalte

Länge

Die Einhaltung der maximalen Länge von Spaltennamen

Spaltenname Benennung

LID

Datentyp darf nur Integer sein

Spaltenname Benennung

FKLID

Datentyp darf nur Integer sein

Spaltenname Benennung

STRID

Datentyp darf nur String sein

Spaltenname Benennung

FKSTRID

Datentyp darf nur String sein

Rechte

Die für die Applikation benötigten Rechte müssen ausschließlich mit Gruppen definiert werden. Die Gruppen müssen nach folgendem Schema und in englischer Sprache vergeben werden:

Application.<Applikationsname>.<Rolle>

In vielen Applikationen gibt es die folgenden, grundlegenden Rechteobjekte:

Benutzergruppe

Beschreibung

Application.<Applikationsname>.User

Normaler Benutzer der Applikation

Application.<Applikationsname>.Administrator

Administrator der Applikation, der Einstellungen und Stammdaten verwaltet

Application.<Applikationsname>.Manager

Benutzer, die z.B. redaktionelle Aufgaben in einer Applikation übernehmen.

Application.<Applikationsname>.Approver

Prüfer bzw. Freigeber wenn die Applikation einen entsprechenden Prozess besitzt

Application.<Applikationsname>.Reviewer

Gutachter bzw. Personen die zu einem Vorgang ein Feedback geben

Application.<Applikationsname>.Responsible

Verantwortliche wie Abteilungsleiter, die Zugriff auf Daten ihrer eigenen Abteilung erhalten

Die Benutzergruppe "Benutzer" sollte nicht verwendet werden, damit die Rechtesteuerung flexibel bleibt. Im Portal sollte die Rolle bei Bedarf auf die User-Rolle in der Applikation gemappt werden. Spezielle Rechteobjekte können je nach Applikation definiert werden. Die verwendeten Rechteobjekte und deren Funktion müssen im Handbuch zur Applikation wie in der Tabelle oben dokumentiert werden. Zur kontextbezogenen Einschränkung der Auswahl von Benutzern steht ein Filter zur Verfügung, der mit den definierten Rechtegruppen belegt werden kann. Die Auswahl der Benutzer über die BenutzerId wird mit "Ist enthalten in" des Systemwerts "Set und enthaltene Sets" und der Angabe der Rechtegruppe eingeschränkt. Wichtig bei dieser Filtervariante ist, dass auch Vererbungen berücksichtigt werden.

Skript

Der mit der Applikation ausgelieferte Programmcode sollte in einem möglichst einheitlichen Stil erfolgen. Methoden, Funktionsnamen, Variablen und Kommentare sind wie folgt zu erstellen:

  • Englisch

  • korrekte Grammatik und Rechtschreibung

  • einheitliche Syntax

  • korrekte CamelCase

JavaScript Codierungs-Konventionen

JavaScript sollte so weit wie möglich vermieden werden. Die Bedingte Anzeige von Gruppierungen und Schaltflächen mit serverseitig ausgeführtem Velocity-Code ist sicherer und unterstützt zudem auch barrierefreie Anforderungen.

Fehlerbehandlung

Bei eigener Fehlerbehandlung muss für das Feedback an den Anwender die Intrexx Notifier-Funktion verwendet werden, um einen einheitlichen Standard einzuhalten.

Notifier.status.notify("No date was specified.", "Note");
Notifier.status.error("The entered cost center does not exist.", "Error");

Velocity Codierungs-Konventionen

Sicherheit

Mit Velocity können u.a. komplette HTML-Konstruktionen erzeugt werden. Bei Fehlern können diese das Laden einer gesamten Seite und sogar des gesamten Portals verhindern. Zudem können über Velocity-Templates Informationen ausgeliefert werden, auf die der Benutzer gemäß dem Intrexx-Framework keine Rechte hat. Daher müssen Velocity-Templates gut geprüft und Fehlverhalten abgefangen werden.

Wenn in Applikationen eigene Velocity-Dateien eingesetzt werden, die z.B. eigene SQL-Abfragen durchführen und damit die normale Berechtigungsstruktur der Intrexx-Businesslogik umgehen, sollte der berechtigte Einsatz des Velocity-Codes geprüft werden. Wird diese Prüfung nicht durchgeführt, kann die Datei einfach über den Browser aufgerufen und der Inhalt ohne Berechtigungsprüfung angezeigt werden. Diese Problematik ähnelt einer SQL-Injection. Benutzereingaben werden ohne Prüfung übernommen und an API-Funktionen übergeben, was es zu verhindern gilt. Der eigene Code sollte mit dem folgenden Konstrukt umschlossen werden, um die Prüfung serverseitig sicherzustellen:

#if($AccessController.hasPagePermission("APP_GUID", "PAGE_GUID", "access"))
 ## Your Code ##
#end

Die GUID der Zielapplikation wird als erster Parameter, die GUID der Zielseite als zweiter Parameter angegeben. Als dritter Parameter wird der Schlüssel "access" eingetragen. Die Parameter sollten fest in den Aufruf eingetragen werden. Die Übergabe in Form von Requestwerten dynamisiert die Prüfung – macht Sie jedoch wieder angreifbar. Soll der Zugriff auf eine Datengruppe in Velocity geprüft werden (bevor ein Datenbankzugriff durchgeführt wird), kann folgendes Konstrukt um die eigentliche Funktion gelegt werden:

#if($AccessController.hasDatagroupPermission($ProcessingContext,"APP_GUID", "DATAGROUP_GUID", "read"))
 ## Your Code ##
#end

Mit dem ersten Parameter wird der Processing-Context übergeben. Die GUID der Zielapplikation wird als zweiter Parameter, die GUID der Datengruppe als dritter Parameter angegeben. Als vierter Parameter muss einer der nachfolgend aufgeführten Schlüssel eingetragen werden:

Schlüssel

Funktion

create

Erstellen

delete

Löschen

delete-own

Löschen eigener Daten

read

Lesen

read-own

Lesen eigener Daten

write

Ändern

write-own

Ändern eigener Daten

Die Parameter sollten fest in den Aufruf eingetragen werden. Die Übergabe in Form von Requestwerten dynamisiert die Prüfung, macht Sie jedoch wieder angreifbar.

Velocity-Includes

Velocity-Dateien sollten immer im Applikationspaket enthalten sein, um einen einfachen Import zu gewährleisten. Falls es übergreifende Velocity-Templates gibt, sind diese im Portalverzeichnis internal/system/vm/html/include und dort in einem eigenen Unterverzeichnis abzulegen. Alle anderen Verzeichnisse sind für Intrexx reserviert.

Groovy Codierungs-Konventionen

Dokumentation der Codes

Die Codes in JavaScript, Velocity oder Groovy sollten ausführlich dokumentiert werden. D.h. in erster Linie ausreichend sinnvolle Kommentare im Code einbauen, um dessen Funktion ersehen zu können. Nutzen Sie auch die Möglichkeit JSDOC-Kommentare einzufügen.

Prepared Query

Die korrekte Anwendung von PreparedQuery zur Ausführung von SQL-Statements in Groovy und Velocity behindert einen Angriff via SQL-Injection. Das Wissen über die Auswirkung und Beeinflussung von Parametern, die in das SQL-Statement gelangen, sind eine wichtige Voraussetzung. Intrexx bietet eine Fülle von Funktionen zur Anwendung von PreparedQuery. Allerdings können falsche Anwendung Lücken erzeugen, die es zu vermeiden gilt. In Groovy sieht eine korrekte PreparedQuery wie folgt aus. Wichtig ist, dass die Werte in der WHERE-Klausel über die Platzhalter ? definiert und im Anschluss über set-Methoden gesetzt werden. Die jeweiligen set-Methoden stellen sicher, dass auch das erwartete Format eingefügt wird. D.h. wird anstatt eines Ganzzahlwertes eine Zeichenkette eingeschleust, wird dieser nicht verarbeitet und verursacht einen Fehler. Die detaillierte Fehlermeldung muss im Portal unterdrückt werden, damit der Effekt volle Wirkung entfaltet.


def conn = g_dbConnections.systemConnection 
def stmtUpdate 
def stmt = g_dbQuery.prepare(conn, "SELECT LID FROM DSUSER") 
def rs = stmt.executeQuery() 
while (rs.next()) 
{ 
	stmtUpdate = g_dbQuery.prepare(conn, "UPDATE DSUSER SET ... WHERE LID = ?'") 
	stmtUpdate.setInt(1, rs.getIntValue(1)) 
	stmtUpdate.executeUpdate() 
	stmtUpdate.close() 
} 
rs.close() 
stmt.close()

Das nachfolgende Konstrukt ist falsch definiert und birgt ein hohes Risiko.

def conn = g_dbConnections.systemConnection

def l_UserId = g_record["GUID"].value

def stmt = g_dbQuery.prepare(conn, "SELECT DTBIRTH FROM DSUSER WHERE LID = '${l_UserId}'")
def rs = stmt.executeQuery()

while (rs.next())
{
  rs.getDateValue(1)
}

rs.close()
stmt.close()

Der Wert aus dem Datenfeld kann von einem Hacker manipuliert werden und anstatt der erwarteten LID eine Zeichenkette mit der Erweiterung der WHERE-Abfrage einschleusen.

Erwartet:

WHERE LID = 10

Manipuliert:

WHERE LID = 10 OR LID > 0

Mit dieser Veränderung bekommt der Hacker alle Datenbankeinträge in der Benutzerdatenbank. Werden die Request-Werte zusätzlich mittels Validatoren geprüft, kommen z.B. Zeichenketten über Request-Werte die Ganzzahlwerte liefern sollen schon nicht so weit, dass sie in das Statement eingesetzt werden. Ebenfalls eine sehr kritische Konstruktion ist nachfolgend gezeigt. Das SQL-Statement wird durch das Zusammenfügen einer Zeichenkette und einer Variablen gebildet. Auch hier kann durch die Variable, die zudem über einen Requestwert übergeben wird, zur Erweiterung der WHERE-Klausel missbraucht werden.

def l_request = g_request.get("rq_myparameter")

def l_sql = "SELECT DTBIRTH FROM DSUSER WHERE LID = "l_sql += l_request

def stmt = g_dbQuery.prepare(conn, l_sql)
def rs = stmt.executeQuery()

...

Im Velocity-Umfeld gelten dieselben Bedingungen. Auch hier muss das PreparedQuery korrekt angewendet werden:

#set($UserId = $DC.getValueHolder("GUID").getValue())

#set($stmt = $PreparedQuery.prepare($DbConnection, 
"SELECT DTBIRTH FROM DSUSER WHERE LID = ?"))
$stmt.setInt(1, $UserId)
#set($rs = $stmt.executeQuery())

#foreach($element in $rs)
  $element.getDateValue(1)
#end

$rs.close()
$statement.close()

Ein ebenfalls kritisches Konstrukt ist die Definition einer Query, bei der die Datenfeldnamen dynamisch eingesetzt werden. Insbesondere, wenn die Feldnamen über Daten aus dem Request oder anderen äußeren Quellen bestimmt werden. Dies gilt für Groovy und Velocity.

def ergebnis = g_dbQuery.executeAndGetScalarIntValue(conn, 
"SELECT count(*) FROM DATAGROUP WHERE LID = ? AND $FIELDNAME1 IS NOT NULL AND $FIELDNAME <> ''",0)

Bei einer solchen Lösung müssen viele Sicherheitsstufen vorgesehen werden. Die ideale Lösung ist, solche Konstrukte zu vermeiden. Folgende Sicherheitsmaßnahmen wären angeraten:

  1. Der Client sendet beliebige eindeutige Schlüsselwerte für die einzelnen Felder an den Server. Die Schlüsselwerte können zum Beispiel die GUIDs von Datenfeldnamen oder Querystring-Parametern sein.

  2. Über entsprechende Validatoren werden die übermittelten Werte bereits vorab geprüft (z.B. Whitelist). Wird keine Übereinstimmung gefunden, erfolgt keine serverseitige Verarbeitung.

  3. Der Server prüft, ob der angemeldete Benutzer über die notwendigen Rechte verfügt, den nachfolgenden Code auszuführen (im Normalfall Leserechte an der Datengruppe).

  4. Der Server schlägt in einer Liste die erlaubten Feld-GUIDs nach. Trifft er dabei auf einen nicht zugeordneten Parameter, wirft er eine Exception oder ignoriert den Parameter - je nach Anforderung.

  5. Anhand der Feld-GUIDs werden die zugehörigen Spaltennamen in der Datenbank ermittelt und ein Prepared-Statement aufgebaut.

  6. Die aktuellen Werte werden an das Prepared-Statement übergeben.

  7. Die Datenbank-Abfrage wird ausgeführt

Mehrsprachige Applikationen

Alle Informationen zu diesem Thema finden Sie hier.

Klonbare Applikationen

Damit eine Applikation per Kopie dupliziert werden kann muss vor allem Groovy- und Velocity-Script korrekt definiert werden. Dies betrifft vor allem die Datengruppen und die SQL-Statements. Intrexx vergibt beim Duplizieren einer Applikation alle GUIDs neu und ändert dabei auch die GUIDs in den Scripten (Groovy, Velocity, JavaScript), damit alles wieder zusammenpasst. Aus diesem Grund müssen die Datengruppendefinitionen immer über die GUID der Datengruppe erfolgen:

def l_strIsoLanguage = it
def l_intLanguageDetect = g_dbQuery.executeAndGetScalarValue(conn, 
"SELECT COUNT(*) FROM DATAGROUP('98C0EC3CC539925C8B7644F4AB726BE2F38038F1')
WHERE LANG = ?", 0) {
  setString(1, l_strIsoLanguage)
}

#set($stmtFloors = $PreparedQuery.prepare($DbConnection, 
"SELECT T0.STRID, T1.STRNAME, T1.STRSHORTNAME FROM
DATAGROUP('B1156946E0CF3215576D4595A757A3FD0BB31C22') T0 LEFT OUTER JOIN
DATAGROUP('1037FA883B005B35FE5D4B8A10645B9FB2B04933') T1 ON ((T0.REF_PROPERTY
= T1.PROPID) AND (T1.LANG = ?)) WHERE T0.STRPARENTID = ? AND T0.REF_CLASS = ?
ORDER BY T1.LSORT"))
$stmtFloors.setString(1, $lang)
$stmtFloors.setString(2, $Builiding)
$stmtFloors.setString(3, "LEVEL")
#set($rsFloors = $stmtFloors.executeQuery())

Für den Zugriff auf Datenfelder auf Applikationsseiten im Velocity-Kontext sollte ebenfalls mit den GUIDs gearbeitet werden. Dazu muss in den Settings der jeweiligen Seite der Schlüssel "page.requiredDataFields.mode" mit dem Wert "all" gesetzt werden. Dies ist die schnellste Methode, alle Datenfelder der Datengruppe über GUIDs bereitzustellen. Es ist auch die Grundlage für den Zugriff in den Velocity-Templates der vorherigen Abschnitte.

Im Velocity-Kontext kann dann mit den folgenden Methoden auf die gespeicherten Daten zugegriffen werden:

$DC.getValueHolder("GUID_DATA_FIELD").getValue()

## When page is used in a free table
$drRecord.getValueHolder("GUID_DATA_FIELD").getValue()

Der Vorteil dieser Methode ist, dass die Informationen nicht physikalisch als Ansichtsfeld auf der Seite platziert (und ggf. versteckt) werden müssen.

Portierbare Applikationen (Datenbanken)

Da in Verbindung mit Intrexx unterschiedliche Datenbanken eingesetzt werden können, sollten die SQL-Statements möglichst neutral definiert werden, d.h. keine datenbankspezifischen Funktionen. Der ANSI-SQL Standard ist dabei einzuhalten. Falls es keine neutrale Alternative für ein SQL-Statement gibt, muss eine Datenbankweiche für das SQL-Statement definiert werden, die für jede Datenbank das passende Statement verwendet.

Weiche für Groovy:

def conn = g_dbConnections.systemConnection

switch (conn.descriptor.databaseType)
{
	case "Db2":
		// DB2
		break

	case "Derby":
		// Derby/Java DB
		break

	case "Firebird":
		// Firebird
		break

	case "HSQLDB":
		// HSQLDB
		break

	case "Ingres":
		// Ingres
		break

	case "Oracle8":
		// Oracle 8
		break

	case "Oracle9":
		// Oracle 9
		break

	case "Oracle10":
		// Oracle 10
		break

	case "Oracle11":
		// Oracle 11
		break

	case "Oracle12":
		// Oracle 12
		break

	case "PostgreSQL":
		// PostgreSQL
		break

	case "MaxDB":
		// MaxDB
		break

	case "MsSqlServer":
		// Microsoft SQL Server
		break

	case "Standard":
		// unspecified
		break

	default:
		assert false : "Unexpected database type."
		break
}

Weiche für Velocity:

#set($DbName =
$DbUtil.getConnection("IxSysDb").getDescriptor().getDatabaseType())

#if($DbName == "MsSqlServer")
  #set($sql = " SELECT TOP 10 * FROM MyTable ORDER BY LID DESC")
#elseif($DbName == "PostgreSQL")
  #set($sql = "SELECT * FROM MyTable ORDER BY LID DESC LIMIT 10")
#elseif($DbName == " Oracle9" || $DbName == " Oracle8")
  #set($sql = " SELECT * FROM (SELECT * FROM MyTable ORDER BY LID DESC) 
  WHERE rownum <= 10")
#else
  $Debug.info("Unexpected database type")
#end

Prozesse

Bei Prozessen sollte Folgendes beachtet werden: Timer-Aktionen auf Datengruppen sollten wenn möglich die Treffermenge über einen Filter einschränken, um die Anzahl der Datenbankoperationen gering zu halten. Im Auslieferungszustand sollte in den Prozess-Eigenschaften die Einstellung "Nur Warnungen und Fehler protokollieren" gesetzt sein. Zum Debugging eingefügte g_log.info() können im Code verbleiben. Sie werden im Regelbetrieb ignoriert und halten die Log-Dateien klein.