Das mentale Modell hinter allem
Archestack hat eine kleine Anzahl an Kernkonzepten, die überall im Produkt auftauchen. Jedes löst ein Problem, das du sonst von Hand wieder und wieder lösen müsstest, jedes Mal ein bisschen anders. Fünfzehn Minuten hier zu investieren spart dir Stunden des Herumprobierens. Jeder Abschnitt unten führt ein Konzept ein, zeigt ein durchgearbeitetes Beispiel und benennt die Fehler, die neue Nutzer machen.
Das Schema ist die Single Source of Truth
Alles beginnt mit dem Schema, einer JSON-Beschreibung jeder Tabelle, Spalte, Beziehung und jedes Index in deiner Anwendung. Du schreibst kein SQL von Hand. Du entwirfst Tabellen im visuellen Schema Designer, und Archestack wandelt das beim Deployen in echte PostgreSQL-Tabellen um.
Warum es das gibt
Ein klassisches ERP ist ein Dickicht aus Tabellen, die über ein Jahrzehnt organisch gewachsen sind. Archestack dreht das Modell um: Das Schema ist ein versioniertes Artefakt, das du explizit bearbeitest, wie Quellcode. Jedes andere Tool der Plattform, Business Entities, Pages, Events, Scripts, liest aus dem Schema. Existiert eine Spalte nicht im Schema, weiß kein anderes Tool davon.
Wie es in der Praxis funktioniert
- Auto-Save vs. Deploy. Das Bearbeiten im Schema Designer speichert deine Änderungen automatisch (eine Anzeige erscheint unten links). Speichern ändert die Datenbank nicht, das passiert erst, wenn du in Database Deployments ein Deployment anlegst und auf Deploy klickst.
- Generated SQL ist editierbar. Der Generated SQL-Tab des Deployments zeigt das Migrations-SQL, das die Plattform erzeugt hat,
CREATE TABLE,ALTER TABLEusw. Du kannst es vor dem Deployen lesen und direkt bearbeiten, falls die automatisch erzeugte Variante nicht ganz passt. - Pre-/Post-Hooks. Ein Deployment kann Pre-Deployment- und Post-Deployment-Skripte (PostgreSQL) mitführen, nützlich, um eine neue NOT NULL-Spalte zu befüllen oder einen Index in kontrollierter Reihenfolge neu aufzubauen.
- Die Historie bleibt erhalten. Jedes Deployment wird mit Status (Draft / Executing / Succeeded / Failed) und ausgeführtem SQL protokolliert.
Durchgearbeitetes Beispiel
Du fügst eine priority_score-Spalte zu deal hinzu. Im Schema Designer
heißt das: Tabelle auswählen, den Columns-Tab öffnen, den Namen ins
"column_name"-Feld tippen, INTEGER aus dem Type-Dropdown wählen, auf Add Column
klicken. Die Auto-Save-Anzeige zeigt kurz "Auto saving…" und dann "Saved". Klicke dann in der
Symbolleiste auf Deploy, du landest auf einer frischen Deployment-Konfigurationsseite.
Öffne den Generated SQL-Tab:
ALTER TABLE "deal" ADD COLUMN "priority_score" INTEGER; Klicke oben rechts auf Deploy, fertig. Die Spalte existiert jetzt. Solange du sie aber nicht auch zur deal-Business Entity hinzufügst (nächstes Konzept), sieht sie keine Page und kein Event. Diese Trennung ist beabsichtigt: Schemaänderungen sind billig, sie sichtbar zu machen ist der bewusste nächste Schritt.
Stolperfallen
- Eine Spalte umzubenennen droppt sie und legt sie neu an. Der Plan-Generator kann nicht hellsehen. Wenn du umbenennst, bearbeite das SQL im Generated SQL-Tab und nutze
RENAME COLUMN, oder mach ein mehrstufiges Deployment: neue Spalte anlegen, in einem Post-Script die Daten kopieren, alte Spalte in einem Folge-Deployment droppen. - Eine NOT NULL-Spalte in eine nicht-leere Tabelle einzufügen schlägt fehl. Entweder gibst du einen Default an, oder du legst sie zuerst nullable an, befüllst sie nach und änderst sie dann im zweiten Deployment auf NOT NULL.
- Fremdschlüssel schränken das Löschverhalten ein. Nutze das On Delete-Dropdown bei jeder Beziehung bewusst, die Optionen sind CASCADE, SET NULL, SET DEFAULT, RESTRICT, NO ACTION.
- snake_case ist die Konvention. Das "Table name"-Feld im Schema Designer deutet es sogar an ("Lowercase with underscores recommended"). Die Tools funktionieren auch mit PascalCase, aber jedes Codebeispiel auf dieser Site geht von snake_case für Tabellen- und Spaltennamen aus.
Business Entities sind die kuratierte Sicht
Eine rohe Tabelle (customer) ist selten das, was ein Endbenutzer sehen will. Er
will "customer mit dem Gesamtbetrag der letzten Bestellung" oder "vehicle mit
Name und Telefonnummer des zugewiesenen Mechanikers". Eine
Business Entity (BE) ist eine kuratierte Sicht auf eine Quelltabelle plus
angedockte Spalten aus verwandten Tabellen. Pages werden auf BEs aufgebaut, nicht auf rohen
Tabellen.
Warum es das gibt
Zwei Gründe. Erstens wäre das Joinen von Tabellen auf jeder Seite mühsam und inkonsistent,
verschiedene Seiten würden dieselben Tabellen leicht unterschiedlich joinen, und das Verhalten
würde auseinanderdriften. Die BE zentralisiert die Join-Logik. Zweitens willst du oft mehrere
"Sichten" auf dieselbe Quelltabelle für unterschiedliche Zielgruppen: Die Sales-Sicht auf
customer zeigt Umsatz und letzten Kontakt; die Support-Sicht zeigt Tickets und
SLA-Verletzungen. Verschiedene Seiten referenzieren verschiedene BEs, alle gestützt auf dieselbe
Zeile in customer.
Anatomie
- Master Table - die Tabelle, auf der die BE verankert ist. Jede BE hat genau eine. Der Primärschlüssel der BE ist der Primärschlüssel der Master-Tabelle.
- Label Column - eine Spalte, deren Wert einen Datensatz in Listen und Pickern identifiziert (typischerweise
name,titleoder Ähnliches). Wird beim Anlegen der BE ausgewählt. - Joins - über den Add Join-Button hinzugefügt. Jeder Join hat einen Join-Typ (INNER / LEFT / RIGHT), eine Zieltabelle, Quell- und Zielspalten und eine Auswahl, welche Zielspalten nach außen sichtbar werden.
- Aggregierte Spalten - Aggregate Mode an einem Join einschalten, dann pro Spalte eine Aggregatfunktion wählen: COUNT, SUM, AVG, MIN, MAX, COUNT DISTINCT. "Anzahl offener Kontakte zu dieser Company" ist eine typische aggregierte Spalte.
Durchgearbeitetes Beispiel
Master-Tabelle: vehicle. Sichtbare native Spalten: vin,
make, model, year. Join zu customer über
vehicle.owner_id → customer.id, gibt customer.full_name als
owner_name heraus. Zweiter Join im Aggregate Mode: Anzahl der
work_order-Zeilen, bei denen vehicle_id übereinstimmt und
status != 'Closed' gilt, ausgegeben als open_work_order_count.
Eine Page, die an diese BE gebunden ist, rendert eine einzelne Grid-Zeile, die aussieht wie ein flacher Fakt, obwohl sie aus drei Tabellen zusammengezogen wird, und das durchgehend konsistent über die ganze Plattform.
Stolperfallen
- BEs sind standardmäßig keine Schreibgrenzen. Wenn du aus einem BE-gebundenen Formular speicherst, wird in die Master-Tabelle geschrieben. Gejointe Spalten sind normalerweise schreibgeschützt, um
owner_nameim Beispiel oben zu bearbeiten, geh zum Customer. - Nutze Run Preview großzügig. Der Button Run Preview im BE-Editor führt die Join-Konfiguration aus und zeigt echte Zeilen. Wenn eine Join-Spalte leer zurückkommt, ist deine Beziehung oder dein Spalten-Mapping falsch konfiguriert.
- Greif nicht über zu viele Ebenen. Eine BE, die drei Tabellen tief joint, fühlt sich auf großen Datenmengen langsam an. Wenn du dich dabei ertappst, vier oder fünf Tabellen zu joinen, ist das ein Hinweis, dass du eigentlich ein Custom Script Module oder eine Datenbank-View willst.
Pages werden konfiguriert, nicht programmiert
Eine Page ist eine Laufzeit-UI, gebaut aus einer oder mehreren Business
Entities. Du konfigurierst sie im Page Editor, indem du Namen, Route (z. B.
/companies), eine Business-Entity-Bindung und einen Published-Schalter setzt. Die
Plattform erzeugt automatisch Abschnitte aus den BE-Spalten; von da aus feinjustierst du das
Layout.
Anatomie
- Visual-Tab - der WYSIWYG-Layout-Editor. Sections, Felder, Tabs, Action-Buttons.
- Overview-Tab - Konfiguration der Liste bzw. des Grids: welche Spalten erscheinen, Standardsortierung, Filter.
- Create-Tab - das Formular für neue Datensätze. Unterscheidet sich oft leicht vom Detailformular (weniger Felder, andere Defaults).
- Entities-Tab - weitere BE-Bindungen (die Page kann mehr als eine BE für Tabs und verwandte Grids referenzieren).
- Events-Tab - Event Triggers, die nur in dieser Page gelten.
- JSON-Tab - rohes JSON für Power-User.
Field-Widget-Typen
Für jedes Feld im Detailformular wählst du einen Type aus einem Select-Control. Die konkreten Optionen:
Text- einzeiliges EingabefeldTextarea- mehrzeiliges EingabefeldNumber- numerisches EingabefeldDate- DatumsauswahlSelect- Dropdown (nimm das für Fremdschlüssel-Felder und Enum-artige Spalten; bei FK-Feldern setze das Entity-Autocomplete auf die referenzierte BE, damit Nutzer per Label auswählen)Checkbox- boolescher WertEmail- Texteingabe mit E-Mail-Validierung
Für jedes Feld konfigurierst du außerdem Label, Placeholder, Required, Read Only und Span (Breite im Grid).
Tabs und verwandte Grids
Das Detailformular einer Page unterstützt Tabs. Nutze den Add Tab-Button,
um einen hinzuzufügen. Innerhalb eines Tabs kannst du eine RelatedGrid-Sektion
hinzufügen, die Zeilen aus einer anderen BE zeigt, gefiltert über einen Join (z. B. alle
contact-Zeilen, bei denen company_id = current company's id ist). So
baust du "Company → Contacts / Deals"-Master-Detail-Seiten ganz ohne Code.
Publishing
Jede Page hat im oberen Header einen Published-Switch. AUS = Entwurf (nur du siehst deine Änderungen im Editor); AN = die veröffentlichte Page erscheint in der Sidebar unter Published Pages und ist das, was Endnutzer sehen, wenn sie deren Route aufrufen.
Frontend Templates: Wenn "konfigurieren, nicht programmieren" nicht reicht
Manchmal reichen die konfigurierten Templates nicht, du willst ein eigenes Widget, ein ungewöhnliches Layout oder eine spezielle Visualisierung. Frontend Templates lassen dich ein Stück TSX schreiben, das im Page Editor als wiederverwendbare Komponente bereitsteht. Sie sind paketierbar und liegen neben dem Rest deiner Konfiguration. Siehe Reference → Frontend Templates.
Stolperfallen
- Den Published-Switch vergessen? Die Page erscheint nicht in der Sidebar, und Endnutzer, die ihre Route aufrufen, sehen einen 404. Leicht zu übersehen, weil der Editor das nicht sichtbar erzwingt.
- Breite zählt. Wenn deine BE 25 Spalten hat, ist das Grid auf einem Laptop unbenutzbar. Blende Spalten mit geringem Wert aus; sie bleiben abfragbar, nur eben nicht angezeigt.
- Tabs lösen eigene Queries aus. Eine Page mit fünf Tabs macht beim Öffnen einer Detailzeile zusätzliche Queries. Normalerweise unproblematisch, bei großen Datenmengen aber manchmal ein Performance-Thema.
Business Events lassen Daten reagieren
Ein Business Event beobachtet eine Business Entity auf Änderungen und führt Actions aus, wenn seine Conditions erfüllt sind. Das sind die "wenn X passiert, tu Y"-Regeln, die aus einer passiven Datenbank eine arbeitende Anwendung machen.
Trigger-Zeitpunkte
Die konkreten Optionen beim Anlegen eines Events (du kannst mehrere ankreuzen):
BeforeCreate- feuert, bevor ein neuer Datensatz gespeichert wird. Das Skript kannEntityverändern, die Änderungen werden mitpersistiert.BeforeUpdate- feuert, bevor ein bestehender Datensatz gespeichert wird. Gleiches Verhalten beim Ändern.AfterCreate- feuert, nachdem ein neuer Datensatz committet wurde.AfterUpdate- feuert, nachdem ein bestehender Datensatz committet wurde.BeforeDelete- feuert, bevor ein Datensatz gelöscht wird.InitialValue- feuert, wenn ein neues Formular geöffnet wird; setzt Feld-Defaults.OnSchedule- wird von Quartz nach einem Cron-Zeitplan ausgelöst (siehe Scheduled Events).Manual- wird explizit über einen Aktions-Button auf einer Page ausgelöst.
Action-Typen
Die konkreten Action-Typen (RuleActionKind):
ExecuteScript- führt ein C#-Skript aus. Die flexibelste Action; danach greifst du, wenn du ein Feld setzen, eine Berechnung machen, eine API aufrufen oder sonst irgendwas Eigenes tun willst. Das Skript bekommtEntity(ändere es, um den Datensatz anzupassen),OldEntity,Log,Db,Modules.Validate- führt ein C#-Skript aus, dasboolzurückgibt.truebedeutet, dass die Validierung fehlschlägt und das Speichern mit der konfigurierten Fehlermeldung blockiert wird. Hebt optional bestimmte Spalten hervor.BlockOperation- bricht die Operation hart mit einer Meldung ab. Kein Skript.CreateEntity- fügt einen Datensatz in eine andere BE ein. Feldwerte unterstützen Template-Ausdrücke.UpdateEntity- aktualisiert Datensätze in einer anderen BE, die auf einen Bedingungsfilter passen.DeleteEntity- löscht Datensätze, die auf eine Bedingung passen (verweigert ohne Bedingung, zur Sicherheit).SendEmail,SendWebhook,PublishEvent- im Schema als zukünftige Erweiterungen definiert; aktuell geloggt und übersprungen.
Template-Ausdrücke
Die Action-Konfiguration akzeptiert Template-Ausdrücke in {{ … }}-Klammern. Die konkreten Tokens:
{{ Entity.column_name }}- Feld des aktuellen Datensatzes ({{ Entity.id }}funktioniert auch){{ OldEntity.column_name }}- vorheriger Wert (nur Update-Trigger){{ now() }}oder{{ getdate() }}- ISO 8601 UTC-Datetime{{ today() }}- Datums-String{{ guid() }}oder{{ newid() }}- frische GUID{{ year() }},{{ month() }},{{ day() }},{{ timestamp() }}- Aggregate in einem Join-Kontext:
{{ SUM(column) }},{{ AVG() }},{{ COUNT() }},{{ MIN() }},{{ MAX() }} - Arithmetik:
{{ Entity.quantity * Entity.price }}- nach der Substitution ausgewertet
Hinweis: Es gibt kein {{ user.email }}-Token,
Skripte und Template-Ausdrücke haben keinen Zugriff auf den aktuellen Nutzer. Wenn du in einer
Regel die Benutzeridentität brauchst, speichere sie beim Einfügen aus dem Frontend mit auf den
Datensatz und lies sie von dort.
Durchgearbeitete Beispiele
- Ein Feld beim Speichern normalisieren.
Trigger: Before Update auf Deal. Keine Bedingungen.
Action: Execute Script mit
Entity.title = ((string)Entity.title)?.Trim();, um überflüssige Leerzeichen zu entfernen. (Fürcreated_at/updated_at/created_by/updated_bybrauchst du keine Regel, die Plattform stempelt diese automatisch.) - Verhindern, dass Deals mit niedrigem Betrag als Won markiert werden.
Trigger: Before Update auf Deal.
Condition:
stage = 'Won' AND amount < 100. Action: Block Operation mit der Meldung "Deals unter 100 € können nicht als Won markiert werden, log sie als Lost oder lösche sie." - Eine Note anlegen, wenn ein Customer geflaggt wird.
Trigger: After Update auf Customer.
Condition:
flagged = true. Action: Create Entity aufnotemittitle = "Customer flagged at {{ now() }}",body = "Auto-generated for {{ Entity.full_name }}".
Simulieren, bevor du speicherst
Der Trigger-Editor hat einen Simulate-Tab mit einem Button mit der Beschriftung Run Simulation. Wähle einen echten Datensatz aus, und die Plattform führt die Conditions und Actions dagegen aus, ohne Änderungen zu persistieren, Schreiboperationen laufen in einer Transaktion, die zurückgerollt wird. Das Ausgabe-Panel zeigt, welche Conditions zugetroffen haben und was jede Action getan hätte. Nutze das, bevor du einen Trigger auf Produktivdaten aktivierst.
Stolperfallen
- Rekursive Trigger. Ein After Update-Trigger, der per Update Entity denselben Datensatz aktualisiert, feuert sich selbst erneut. Nimm stattdessen Before Update + Execute Script +
Entity.field = …, damit änderst du den laufenden Schreibvorgang, statt einen neuen anzustoßen. - Die Reihenfolge zwischen Triggern zählt. Mehrere Trigger auf demselben Event feuern in Prioritätsreihenfolge. Hängt das Verhalten von der Reihenfolge ab, setze die Prioritäten explizit.
- Deaktivierte Events stehen trotzdem in der Liste. Der Enabled-Switch ist von der Trigger-Konfiguration getrennt, stell vor dem Testen sicher, dass er an ist.
Script Modules sind deine Escape-Hatches
Für alles, was sich nicht als einfache Kombination aus Condition + Action ausdrücken lässt, einen komplexen Rabatt berechnen, eine externe API aufrufen, einen Slug erzeugen, eine mehrstufige Datenbankoperation durchführen, schreibst du ein Script Module: ein kleines Stück C# (zur Laufzeit von Roslyn kompiliert), das du aus einem Business Event, aus einem Scheduled Event oder aus dem Frontend aufrufen kannst.
Worauf Skripte Zugriff haben
Jedes Skript bekommt diese Globals (andere Namen existieren im Skript-Scope nicht):
Entity- der aktuelle Datensatz (in Trigger-Kontexten) als Dynamic. Auf Felder greifst du mitEntity.column_namezu.Entityin einem Before*-Trigger zu ändern ist die Art, ein Feld zu "setzen".OldEntity- der vorherige Datensatz (Update- und Delete-Trigger). Nur lesbar.Log- einILoggerfür Diagnose.Log.LogInformation("…")schreibt in die Server-Logs und in den Event-Log-Eintrag.Db- der Datenbank-Helper. Siehe unten.Modules- andere Script Modules aufrufen:await Modules.CallAsync("OtherModule", new Dictionary<string, object?> { ["param"] = value }).Pdf- ein gespeichertes PDF Template per Name rendern:await Pdf.GenerateAsync("invoice", new Dictionary<string, string> { ["id"] = Entity.id.ToString() })gibt das gerendertebyte[]zurück.Pdf.GenerateBase64Async(...)gibt dieselbe Nutzlast base64-kodiert zurück.
Es gibt kein User-, Http- oder Email-Global.
Wie ein Skript aussieht
// Parameters declared in the UI: customer_id (int)
var customer = await Db.GetAsync("customer", customer_id);
if (customer == null) return new { ok = false, error = "Customer not found" };
var orders = await Db.From("sales_order")
.Where("customer_id", "=", customer_id)
.Where("status", "=", "Closed")
.ToListAsync();
decimal totalRevenue = 0;
foreach (var o in orders) totalRevenue += (decimal)(o.total ?? 0);
return new { ok = true, customer = customer.name, revenue = totalRevenue };
Die meisten Skripte sind 5-30 Zeilen lang. Du brauchst keine .NET-Expertise, du brauchst
grundlegenden C#-Kontrollfluss und den Db-Helper. IntelliSense ist an, und nach
einer Zuweisung aus Db.GetAsync("customer", …) kennt der Editor die Spalten der
Variable und bietet Vervollständigungen an.
Wann du zu einem Skript greifst
- Berechnungen, die mehrere Tabellen umspannen (Lifetime Value eines Customers, Summe der Reparaturhistorie eines Vehicles).
- Aufrufe externer APIs (Geocoding, Zahlungsanbieter, SMS-Gateways).
- Massenoperationen, ausgelöst von einem Scheduled Event (nächtliche Neuberechnung, wöchentlicher Digest).
- Daten ans Frontend zurückgeben, die zu speziell für eine Business Entity sind.
- Alles, wofür du sonst sechs Business Events aneinanderhängen würdest, meist ein Zeichen, dass du eigentlich ein Skript wolltest.
Stolperfallen
- Der Db-Helper ist async. Immer
awaiten. Vergisst du das, kompiliert es, gibt aber einen Task zurück, nicht die Daten. - Es gibt kein
FirstOrDefaultAsyncoderSumAsync. Die Terminale sindToListAsync(),FirstAsync()undCountAsync(). Für eine Summe holst du die Zeilen und summierst in C#. Wherenimmt drei Argumente - Spalte, Operator, Wert. Operatoren sind unter anderem=, !=, >, >=, <, <=, LIKE, ILIKE, IN, IS NULL, IS NOT NULL.- Update ist ein Top-Level-Aufruf:
await Db.UpdateAsync("table", id, new { field = value }), nicht an einWheregekettet. - Trial-Skripte laufen in derselben Sandbox wie in Produktion. Verlass dich nicht darauf, dass "es ist ja nur ein Trial" lockerere Limits bedeutet, dein Skript kann Postgres trotzdem in die Knie zwingen.
Scheduled Events laufen nach der Uhr
Ein Scheduled Event ist dasselbe Event-Trigger-System mit dem OnSchedule-Timing,
ein Quartz-Cron-Ausdruck löst die Regel wiederkehrend aus, statt als Reaktion auf eine
Datenänderung. Nimm es für nächtliche Zusammenfassungen, tägliche Datenaktualisierungen,
wöchentliche Aufräumarbeiten oder monatliche Berichte.
Cron-Format
Quartz-Style mit 6 oder 7 Feldern (second minute hour day-of-month month day-of-week
[year]). Ein paar gängige Muster:
0 0 3 * * ?- jeden Tag um 03:00.0 0 9 ? * MON-FRI- werktags um 09:00.0 */15 * * * ?- alle 15 Minuten.0 0 0 1 * ?- am Ersten jedes Monats um Mitternacht.
Zeiten sind Serverzeit (UTC auf dem Produktions-Stack). Die Scheduled-Events-Seite zeigt den nächsten Ausführungszeitpunkt, damit du vor dem Speichern eine Plausibilitätsprüfung machen kannst.
Stolperfallen
- Langlaufende Jobs blockieren ihren Slot. Wenn ein Job 25 Minuten läuft und der Cron alle 15 Minuten feuert, wird der nächste Lauf übersprungen, Quartz lässt nicht zwei Instanzen desselben Jobs gleichzeitig laufen.
- Fehlgeschlagene Läufe werden nicht wiederholt. Sie landen mit der Exception in den Event Logs. Wenn du Retries brauchst, baue sie explizit in dein Skript ein.
Packages bündeln Konfiguration zur Weitergabe
Sobald du etwas gebaut hast, eine Reihe von Tabellen, BEs, Pages, Events und Skripten, willst du es vom Trial in eine echte Umgebung verschieben oder mit einem Partner teilen. Ein Package ist ein ZIP ausgewählter Konfigurationsobjekte (mit Kaskadierung: eine Page mitzunehmen zieht automatisch ihre BE mit, die wiederum ihre Tabellen mitzieht), das du exportieren und importieren kannst.
Kaskadierung erklärt
Du fügst die Top-Level-Objekte hinzu, die dich interessieren (meist Pages). Der Package-Builder läuft den Abhängigkeitsgraphen ab und ergänzt alles, was diese Objekte brauchen: BEs, an die die Pages binden, Tabellen, die die BEs referenzieren, Frontend Templates, die die Pages einbetten, Script Modules, die die Events aufrufen. Du siehst vor dem Export die vollständige kaskadierte Liste und kannst alles abwählen, was du weglassen willst.
Seed-Daten mitliefern
Packages können optional Zeilendaten enthalten, nützlich, um Standard-Lookup-Tabellen (Länder, Währungen, Status-Enums) oder Demo-Daten auszuliefern. Aktiviere Include data beim Export. Beim Import werden die Zeilen per Primärschlüssel als Upsert eingespielt.
Stolperfallen
- Schema-Deltas werden beim Import nicht automatisch deployt. Fehlen in der importierenden Umgebung Tabellen, scheitert der Import. Deploye zuerst das Schema und importiere danach das Package.
- Identifikatoren laufen über den Namen, nicht über die ID. Eine BE namens "customer" in der Quellumgebung bindet an eine BE namens "customer" in der Zielumgebung, nicht an dieselbe numerische ID. Umbenennungen auf einer Seite brechen die Verbindung.
Business Units steuern, wer was sieht
Für Multi-Tenant- oder Multi-Team-Setups sind Business Units Keycloak-Gruppen, die bestimmten Ressourcen zugewiesen werden können. Ein Nutzer in der Business Unit "Garage A" sieht nur Pages, BEs und Trigger, die Garage A zugewiesen sind.
Wie es funktioniert
Jeder Nutzer gehört einer oder mehreren BUs an (verwaltet in User
Management und Business Units). Das Frontend
merkt sich die aktive BU des Nutzers und setzt bei jeder Anfrage den
X-Business-Unit-HTTP-Header. Das Backend nutzt diesen Header, um Listen-Endpunkte
zu filtern, es kommen nur Ressourcen zurück, die der aktiven BU zugewiesen sind.
Wann du es einsetzt (und wann nicht)
- Nutze es, wenn verschiedene Teams oder Kunden ihren eigenen Ausschnitt derselben Plattform brauchen, die Mechaniker von Garage A sollen die Werkstattaufträge von Garage B nicht sehen.
- Nutze es nicht als Berechtigungssystem. BUs filtern Sichtbarkeit, nicht Autorisierung. Rollen (admin / owner / editor / user) regeln, wer was tun darf.
- Nutze es nicht für zeilenbasierte Sicherheit innerhalb eines einzelnen Mandanten. Das ist Sache der Filter auf der Business Entity, nicht der BU.
Wie es weitergeht
Mit diesen Konzepten im Gepäck folgt der Rest der Doku ganz natürlich. Das First-App-Tutorial nutzt jedes Konzept dieser Seite. Die Reference geht bei jedem Tool eine Ebene tiefer: was anzuklicken ist, worauf zu achten ist und wann du danach greifst.