Die Landschaft der Low-Code-Entwicklungsplattformen hat sich rasant weiterentwickelt, wobei sich Structr stets an der Spitze der Innovation im Bereich der graphbasierten Anwendungsentwicklung positioniert hat. Heute möchten wir einen der bedeutendsten Fortschritte in der Entwicklung von Structr vorstellen: die Integration der Polyglot Engine von GraalVM, die mit Structr 4.0 eingeführt wurde und die Art und Weise revolutioniert hat, wie Entwickler mit den leistungsstarken graphbasierten Funktionen von Structr arbeiten.
Diese Integration führen wir mit der bevorstehenden Veröffentlichung von Structr Version 6.0 fort, indem wir die Funktionen zusammen mit mehreren wichtigen Verbesserungen erweitern. Die bedeutendsten Neuerungen umfassen ein Upgrade auf GraalVM 24 sowie die umfassende Unterstützung für ECMA-Script-Module, wodurch Entwickler JavaScript-Bibliotheken direkt im integrierten Dateisystem von Structr erstellen und organisieren können. Zudem ermöglicht diese Integration das nahtlose Einbinden vorhandener Codebasen und die Verwendung von Standard-ES6-Importanweisungen in der gesamten polyglotten Umgebung. Der dateisystembasierte Modulansatz geht dabei über JavaScript hinaus und umfasst selbstverständlich auch alle anderen von GraalVM unterstützten Sprachen.
Diese Fortschritte eröffnen Entwicklern neue Möglichkeiten: Sie können Code in Structr-Projekten deutlich besser organisieren und wiederverwenden sowie modulare Bibliotheken erstellen, die zwischen verschiedenen Anwendungen und Komponenten innerhalb der Plattform gemeinsam genutzt werden können. Dabei bleiben die entscheidenden Vorteile der schnellen Entwicklung vollständig erhalten, die die Low-Code-Entwicklung so effektiv machen.

Von veralteten JavaScript-Engines zur modernen polyglotten Laufzeitumgebung
Structr baute lange auf JavaScript-Engines wie Nashorn und Rhino auf, um seine Skriptfunktionen zu realisieren. Diese Engines leisteten in der Anfangszeit gute Dienste, wurden jedoch mit der Zeit zu einem Problem, da sie veraltet waren, nur noch eingeschränkten JavaScript-Support boten und unter Performanceproblemen litten, die moderne Entwicklungsworkflows hinderten.
Die Integration der Polyglot Engine von GraalVM stellt einen grundlegenden Wandel von der Skriptintegration zu einer wirklich modernen, mehrsprachigen Entwicklungsumgebung dar. Wo Entwickler früher durch die Einschränkungen älterer JavaScript-Engines eingeschränkt waren, können sie nun aus mehreren Programmiersprachen wählen – JavaScript mit moderner ES6+-Unterstützung, Python und viele andere – und das alles in einer einheitlichen Laufzeitumgebung, die eine nahtlose Interoperabilität gewährleistet.
Structr's eingebaute Funktionsbibliothek: 200+ Funktionen an Ihren Fingerspitzen
Das Herzstück der GraalVM Integration ist die umfangreiche Funktionsbibliothek von Structr mit über 200 integrierten Funktionen, auf die über die Objekte $ oder Structr zugegriffen werden kann. Diese Funktionen umfassen alles von komplexen Neo4j Cypher-Abfragen und HTTP-API-Integrationen bis hin zur PDF-Generierung und MongoDB-Operationen. Mit der Polyglot Engine von GraalVM steht dieses gesamte Funktionsökosystem in mehreren Programmiersprachen zur Verfügung, was Entwicklern eine enorme Flexibilität bei der Umsetzung der Anwendungslogik bietet.
Die Funktionskategorien umfassen:
- Datenbankoperationen: Direkte Neo4j-Integration mit
$.cypher()und erweiterte Suche mit$.find() - Integration externer APIs: HTTP-Funktionen für nahtlose Servicekommunikation
- Dokumentenerstellung: Dateierstellung (PDF, CSV, Excel, XML usw.) und Dateibearbeitungsfunktionen
- Datenbankkonnektivität: Neo4j, MongoDB, JDBC-Integration und Zugriff auf externe Datenbanken
- Transaktionsmanagement: Erweiterte Transaktionsverarbeitung mit
$.doInNewTransaction()oder$.schedule()
{
// Einfaches Beispiel: Suchen von Benutzern mit Prädikaten
$.find('User', $.predicate.equals('isAdmin', true));
}
Eine vollständige Referenz aller verfügbaren Funktionen finden Sie in der Dokumentation der eingebauten Funktionen von Structr.
Umfassende Beispiele für alle Funktionskategorien finden Sie im Abschnitt Detaillierte Funktionsbeispiele weiter unten.
Graphen-Navigation: Von komplexen Abfragen zu intuitivem Objektzugriff
Einer der leistungsstärksten Aspekte der GraalVM-Integration von Structr ist die Umwandlung komplexer Neo4j-Graphbeziehungen in intuitive Objektnavigation. Anstatt für jeden Beziehungszugriff komplexe Cypher-Abfragen zu schreiben, können Entwickler den Graphen mit einfachen Property-Access-Mustern navigieren, welche sich in jeder Programmiersprache natürlich anfühlen.
Ein Beispiel: In einem Project Management System mit Projects, ProjectTasks und Usern würde man normalerweise komplexe Cypher Queries schreiben. Mit Structr wird es so einfach wie ein normaler Objekt-Propertyzugriff:
{
// Intuitive Navigation durch Graphbeziehungen
let user = $.me; // „me" als Keyword für den authentifizierten Benutzer
let userTasks = user.tasks; // Automatische Auflösung von Beziehungen
userTasks.map(task => task.project);
}
Mit diesem Ansatz können Entwickler das Beste aus beiden Welten kombinieren: Cypher für komplexe Patternmatching und Filterung, dann JavaScript für die Datenverarbeitung und Geschäftslogik. Siehe Siehe Beispiele für erweiterte Graphnavigation für Multi-Hop-Durchläufe und komplexe Muster.
Schema-Methoden: Einbettung polyglotter Logik in Datentypen
Die integration der Polyglot Engine von GraalVM geht über Scripting hinaus und erstreckt sich bis in die Schemadefinition von Structr selbst. Entwickler können benutzerdefinierte Methoden direkt für Datentypen definieren und so wiederverwendbare Geschäftslogik erstellen, die im Datenmodell verankert ist. Diese Methoden können in jeder unterstützten polyglotten Sprache implementiert werden und haben Zugriff auf die gesamte Structr-Funktionsbibliothek.
Es gibt drei verschiedene Arten von Schema-Methoden, die jeweils unterschiedlichen Zwecken innerhalb des Datenmodells von Structr dienen:
- Lifecyle Methoden: Werden von Structr automatisch während Events von Knoten wie
onCreate,onSave,onDeleteund anderen ausgelöst. Diese Methoden werden mit Zugriff auf$.thisim Kontext eines Datenbankknoten aufgerufen, wenn das entsprechende Ereignis eintritt, und ermöglichen so automatische Datenvalidierung, die Verwaltung von Beziehungen oder anderen Prozessen. - Instanzmethoden: Werden explizit für bestimmte Knoteninstanzen aufgerufen, die über
$.thisZugriff auf den aktuellen Knotenkontext haben. Diese Methoden kapseln Geschäftslogik, die auf einzelnen Knoten ausgeführt wird, und können die volle Leistungsfähigkeit der integrierten Funktionen von Structr nutzen. - Statische Methoden: Werden direkt für Datentypen aufgerufen, ohne dass eine bestimmte Knoteninstanz erforderlich ist. Diese Hilfsmethoden eignen sich ideal für Operationen auf Typ-Ebene, wie das Suchen von Knoten mit bestimmten Kriterien oder das Ausführen von Aggregatoperationen über alle Instanzen eines Typs hinweg.
// Einfaches Beispiel für eine Schemamethode
{
// Instanzmethode auf Project Typ
$.this.tasks.push($.create("ProjectTask", {name: "New Task"}));
}
So können Entwickler die Geschäftslogik direkt in ihrem Datenmodell kapseln, wodurch Anwendungen wartungsfreundlicher und die Logik wiederverwendbarer wird. _Umfassende Beispiele für Schema-Methoden einschließlich Python-Implementierungen: Beispiele für Schemamethoden.
Frontend-Integration: Polyglotte Leistungsfähigkeit in Templates
Die gleichen polyglotten Fähigkeiten erstrecken sich nahtlos über den gesamten Stack von Structr, einschließlich des Frontend-Template-Rendering. Entwickler können die Graph-Navigation und interne Funktionen direkt in Templates verwenden und so dynamische, datengesteuerte Interfaces erstellen.
<div class="user-dashboard">
<h2>Meine Projekte</h2>
<div class="projects-container">
${{
let userProjects = $.mergeUnique($.me.tasks.map(task => task.project));
$.include("project-list-template", userProjects, "project");
}}
</div>
</div>
Das zeigt die globale Integration der Polyglot Engine von GraalVM – egal, ob man sich in der Backend-Logik, im Frontend-Rendering oder in Schema-Methoden befindet, die gleichen Navigationsmuster und Funktionen verhalten sich konsistent in allen Kontexten.
Mehrsprachige Unterstützung: Über JavaScript hinaus
Während JavaScript weiterhin die primäre Skriptsprache in Structr bleibt, öffnet die Polyglot Engine von GraalVM die Türen für zusätzliche Programmiersprachen für spezielle Anwendungsfälle. Python bietet beispielsweise leistungsstarke Datenverarbeitungsfunktionen und andere Sprachen können ebenfalls bei Bedarf integriert werden.
python{
def getUserNames():
users = list(map(lambda u: u.name, Structr.find("User")))
return users
getUserNames()
}
Das Besondere an diesem Ansatz ist, dass Entwickler unabhängig von der gewählten Sprache über konsistente APIs wie Structr.find() für Python-Kontexte Zugriff auf die umfassende integrierte Funktionsbibliothek von Structr behalten.
Fazit: Eine neue Ära der Low-Code-Entwicklung
Die Integration von GraalVM in Structr ist mehr als nur ein technisches Upgrade – es ist ein Paradigmenwechsel, der moderne, polyglotte Funktionen in die graphbasierte Low-Code-Entwicklung bringt. Durch die Kombination von intuitiver Objektnavigation, der Flexibilität mehrerer Programmiersprachen und der umfassenden Funktionalität der integrierten Bibliothek von Structr können Entwickler anspruchsvolle Anwendungen mit beispielloser Leichtigkeit und Flexibilität erstellen.
Diese Transformation sorgt dafür, dass Structr an der Spitze von Low-Code-Plattformen bleibt und Entwicklern die Tools zur Verfügung stellt, die sie benötigen, um komplexe Herausforderungen zu bewältigen und gleichzeitig die schnellen Entwicklungsfähigkeiten beizubehalten, die Low-Code-Plattformen so leistungsstark machen.
Detaillierte Funktionsbeispiele
Datenbankoperationen mit JavaScript
Eines der leistungsstärksten Features von Structr ist die direkte Integration mit Neo4j über integrierte Funktionen wie $.cypher() und $.find(). Diese Datenbankoperationen bieten intuitiven Zugriff auf komplexe Graphabfragen und Datenmanipulation.
Komplexe Benutzerabfrage
{
// Admin-Benutzer mit erweiterten Prädikaten suchen
// In diesem Beispiel nur Users mit den Namen "jeff" oder "joe" finden
let adminUsers = $.find('User', $.predicate.and(
$.predicate.equals('isAdmin', true),
$.predicate.or([
$.predicate.equals('name', 'jeff'),
$.predicate.equals('name', 'joe')
])
));
// Raw Cypher mit Parametern ausführen
let query = "MATCH (user:User) WHERE user.name = $userName RETURN user";
let users = $.cypher(query, {userName: 'admin'});
}
Erweiterte Transaktionsverwaltung
Die Funktion $.doInNewTransaction() von Structr zeigt, wie komplexe Datenbankoperationen mit geeigneten Transaktionsgrenzen verwaltet werden können:
Stapelverarbeitung
{
let pageSize = 10;
let pageNo = 1;
$.doInNewTransaction(function() {
let nodes = $.find('User', $.predicate.page(pageNo, pageSize));
nodes.forEach(user => {
$.set(user, {lastProcessed: new Date()});
});
pageNo++;
return (nodes.length > 0);
}, function(error) {
$.log('Fehler aufgetreten:', error.getMessage());
return false;
});
}
Externe API-Integration
Die HTTP-Funktionen von Structr zeigen, wie die Integration externer Dienste nahtlos funktioniert.
RESTful API-Aufrufe
{
// Header konfigurieren und Anfragen stellen
$.addHeader('Authorization', 'Bearer ' + token);
$.addHeader('Content-Type', 'application/json');
// Einfaches GET-Request-Beispiel
let response = $.GET('https://api.example.com/users');
// POST-Request-Beispiel
let postData = JSON.stringify({
name: 'New User',
email: 'user@example.com'
});
let createResponse = $.POST('https://api.example.com/users', postData);
if (createResponse.status === 201) {
let newUser = createResponse.body;
$.log('Benutzer erstellt:', newUser.id);
}
}
MongoDB-Integration
Die Funktion $.mongodb() zeigt, wie externe Datenbankverbindungen nahtlos funktionieren.
MongoDB-Betrieb
{
let collection = $.mongodb('mongodb://localhost', 'testDatabase', 'testCollection');
// Mit BSON-Helper einfügen
collection.insertOne($.bson({
name: 'Test User',
email: 'test@example.com',
created: new Date()
}));
// Komplexe Abfragen
let activeAdmins = collection.find($.bson({
$and: [
{ active: true },
{ role: 'admin' },
{ lastLogin: { $gte: new Date(2023, 0, 1) } }
]
}));
}
Dokumentenerstellung und Dateioperationen
Die Funktionen von Structr zur Dokumentenerstellung, wie beispielsweise die PDF-Erstellung, bieten leistungsstarke Tools zur Inhaltserstellung:
PDF-Generation
{
$.setResponseHeader('Content-Disposition', 'attachment; filename="report.pdf"');
$.setResponseHeader('Cache-Control', 'no-cache');
let mainPage = 'pdf-export-main-page/';
let header = '--header-html ' + $.get('base_url') + '/pdf-export-header-page/';
let footer = '--footer-html ' + $.get('base_url') + '/pdf-export-footer-page/';
let wkhtmlArgs = header + ' ' + footer + ' --disable-smart-shrinking';
// HTML-Seiten in PDF umwandeln
let pdf = $.pdf(mainPage, wkhtmlArgs);
// PDF-Inhalt in einer neuen Datei speichern
let newPDFFile = $.create("File", {
name: 'new-file.pdf'
})
$.setContent(newPDFFile, pdf, "ISO-8859-1");
}
Beispiele für erweiterte Graphnavigation
JavaScript-Grafik-Navigation
Mit der polyglotten Engine von Structr wird die Navigation in Beziehungen so einfach wie der Zugriff auf Objekteigenschaften:
{
// Aktuellen authentifizierten Benutzer abrufen
let user = $.me;
// Direkt zu allen diesem Benutzer zugewiesenen Aufgaben navigieren
// Dies durchläuft automatisch die WORKS_ON-Beziehung
let allUserTasks = user.tasks;
// Von Aufgaben zu ihren übergeordneten Projekten navigieren
// Reduce verwenden, um eindeutige Projekte aus allen Aufgaben zu sammeln
let allProjectsOfUser = $.mergeUnique(user.tasks.map(task => task.project));
// Aufgabenanzahl pro Projekt für den aktuellen Benutzer abrufen
let projectTaskCounts = allProjectsOfUser.map(project => ({
project: project,
taskCount: project.tasks.filter(task => user.tasks.includes(task)).length,
completedTasks: project.tasks.filter(task =>
user.tasks.includes(task) && task.completed
).length
}));
// Projekte finden, bei denen der Benutzer der Hauptmitwirkende ist
let primaryProjects = allProjectsOfUser.filter(project => {
let projectTasks = project.tasks;
let userTasksInProject = projectTasks.filter(task => user.tasks.includes(task));
return userTasksInProject.length > projectTasks.length * 0.5; // >50% der Aufgaben
});
}
Multi-Hop-Navigation
{
// Navigation durch mehrere Beziehungsebenen
let user = $.me;
// Alle Teammitglieder abrufen, die an denselben Projekten arbeiten
let userProjects = user.tasks.map(task => task.project);
let colleagues = [];
for (let project of userProjects) {
let projectUsers = project.tasks
.flatMap(task => task.users)
.filter(colleague => colleague.id !== user.id);
// Eindeutige Kollegen hinzufügen
for (let colleague of projectUsers) {
if (!colleagues.find(c => c.id === colleague.id)) {
colleagues.push(colleague);
}
}
}
// Projektabhängigkeiten durch geteilte Ressourcen finden
let projectDependencies = userProjects.map(project => {
let dependencies = [];
// Alle Benutzer abrufen, die an diesem Projekt arbeiten
let projectUsers = project.tasks.reduce((users, task) => {
task.users.forEach(user => {
if (!users.find(u => u.id === user.id)) {
users.push(user);
}
});
return users;
}, []);
// Andere Projekte finden, an denen diese Benutzer arbeiten
for (let projectUser of projectUsers) {
let otherProjects = projectUser.tasks
.map(task => task.project)
.filter(p => p.id !== project.id);
for (let otherProject of otherProjects) {
if (!dependencies.find(p => p.id === otherProject.id)) {
dependencies.push(otherProject);
}
}
}
return {
project: project,
dependencies: dependencies
};
});
}
Mischen von Graph-Navigation mit Cypher
{
let user = $.me;
// Graph-Navigation für einfache Beziehungstraversierung verwenden
let projectTimeline = user.tasks.map(task => ({
project: task.project.name,
task: task.name,
startDate: task.startDate,
endDate: task.endDate,
status: task.status,
teamSize: task.users.length
}));
// Nach Startdatum sortieren
projectTimeline.sort((a, b) => a.startDate - b.startDate);
// Cypher für komplexe Filterung und Mustersuche verwenden
let qualityQuery = `
MATCH (u:User {id: $userId})-[:WORKS_ON]->(task:ProjectTask)<-[:HAS]-(project:Project)
WHERE project.status = 'active'
AND task.priority IN ['high', 'critical']
AND task.dueDate > timestamp()
RETURN DISTINCT project
`;
let activeHighPriorityProjects = $.cypher(qualityQuery, {userId: user.id});
// Jetzt Structr's JavaScript-Funktionen für Qualitätsanalyse verwenden
let qualityAnalysis = activeHighPriorityProjects.map(project => {
// Alle Aufgaben in diesem Projekt mit Graph-Navigation abrufen
let allProjectTasks = project.tasks;
let userTasks = user.tasks.filter(task => task.project.id === project.id);
// Qualitätsmetriken mit JavaScript berechnen
let overdueTasks = allProjectTasks.filter(task =>
task.dueDate && task.dueDate < Date.now() && task.status !== 'completed'
).length;
let completionRate = allProjectTasks.length > 0
? (allProjectTasks.filter(task => task.status === 'completed').length / allProjectTasks.length * 100).toFixed(1)
: 0;
// Kritische Pfad-Aufgaben mit Graph-Navigation finden
let criticalTasks = allProjectTasks.filter(task =>
task.priority === 'critical' && task.status !== 'completed'
);
// Team-Geschwindigkeit berechnen
let recentlyCompleted = allProjectTasks.filter(task =>
task.status === 'completed' &&
task.completedDate &&
task.completedDate > Date.now() - (7 * 24 * 60 * 60 * 1000) // Letzte 7 Tage
).length;
return {
projectName: project.name,
projectDeadline: project.deadline,
totalTasks: allProjectTasks.length,
userTasks: userTasks.length,
overdueTasks: overdueTasks,
completionRate: completionRate,
criticalTasksRemaining: criticalTasks.length,
weeklyVelocity: recentlyCompleted,
riskLevel: overdueTasks > 3 || criticalTasks.length > 1 ? 'high' : 'low'
};
});
}
Beispiele für Schema-Methoden
Instanzmethoden auf Datentypen
Schemamethoden können für jeden Datentyp definiert und direkt auf Knoteninstanzen aufgerufen werden, die über Funktionen wie $.cypher() oder $.find() abgerufen werden. Diese Methoden haben über $.this Zugriff auf den aktuellen Knotenkontext und können die volle Leistungsfähigkeit der eingebauten Funktionen von Structr nutzen.
Beispiel - Projekttyp mit addNewTask-Methode
// Methode definiert auf Project-Schema-Typ
{
// Fügt eine neue ProjectTask hinzu und verbindet sie mit dem Projektknoten
$.this.tasks.push($.create("ProjectTask", {
name: $.methodsParameters.name ?? "New Task",
dueDate: new Date(2026, 01, 01)
}));
}
{
// Ein Projekt finden und die benutzerdefinierte Methode aufrufen
let project = $.find('Project', {name: 'Website Redesign'})[0];
project.addNewTask({name: `New Task for ${project.name}`}); // Ruft die Schema-Methode auf
// Oder mit Cypher-Ergebnissen verwenden
let urgentProjects = $.cypher('MATCH (p:Project) WHERE p.priority = "urgent" RETURN p');
urgentProjects.forEach(project => {
project.addNewTask({name: `New Task for ${project.id}`}); // Jedes Projekt erhält eine neue Aufgabe
});
}
Beispiele für erweiterte Schemamethoden
Projekttyp - Projektstatus berechnen
// Schema-Methode: calculateHealth()
{
let totalTasks = $.this.tasks.length;
let completedTasks = $.this.tasks.filter(task => task.status === 'completed').length;
let overdueTasks = $.this.tasks.filter(task =>
task.dueDate && task.dueDate < Date.now() && task.status !== 'completed'
).length;
let completionRate = totalTasks > 0 ? (completedTasks / totalTasks) : 0;
let healthScore = Math.max(0, completionRate * 100 - (overdueTasks * 10));
return {
healthScore: healthScore.toFixed(1),
status: healthScore > 80 ? 'excellent' : healthScore > 60 ? 'good' : 'needs attention',
totalTasks: totalTasks,
completedTasks: completedTasks,
overdueTasks: overdueTasks
};
}
Benutzertyp – Benutzer-Arbeitsbelastung abrufen
// Schema-Methode: getWorkloadSummary()
{
let activeTasks = $.this.tasks.filter(task => task.status !== 'completed');
let projectMap = {};
activeTasks.forEach(task => {
let projectName = task.project.name;
if (!projectMap[projectName]) {
projectMap[projectName] = {
project: task.project,
taskCount: 0,
highPriorityCount: 0
};
}
projectMap[projectName].taskCount++;
if (task.priority === 'high' || task.priority === 'critical') {
projectMap[projectName].highPriorityCount++;
}
});
return {
totalActiveTasks: activeTasks.length,
projectCount: Object.keys(projectMap).length,
projectBreakdown: Object.values(projectMap)
};
}
Statische Methoden auf Datentypen
Statische Methoden können definiert werden, die keine bestimmte Knoteninstanz erfordern und direkt auf dem Typ aufgerufen werden können:
Projekttyp - Statische Methode zum Auffinden überfälliger Projekte
// Statische Schema-Methode: Project.findOverdueProjects()
{
let currentDate = Date.now();
return $.find('Project', {
deadline: $.predicate.lt(currentDate),
status: $.predicate.not('completed')
});
}
{
// Statische Methode direkt auf dem Typ aufrufen
let overdueProjects = $.Project.findOverdueProjects();
// Ergebnisse mit zusätzlicher Logik verarbeiten
overdueProjects.forEach(project => {
let health = project.calculateHealth(); // Instanzmethode
if (health.healthScore < 50) {
// Benachrichtigung senden oder Maßnahme ergreifen
$.log(`Projekt ${project.name} benötigt sofortige Aufmerksamkeit`);
}
});
}