Definition einer modellgesteuerten Tabelle
Schema-ID: model-driven-entity@1
Maschinenlesbares Schema: model-driven-entity.schema.json
Die maßgebliche Klartextdarstellung einer Tabelle modellgesteuerter Apps. Sie ist die maßgebliche Quelle, gegen die eine Tabelle validiert wird – dieselbe Definition, die der Designer erzeugt, ein API-Aufruf übermittelt, ein KI-Assistent generiert und ein Lösungsimport mitbringt. Die Validierung wird für jede Herkunft identisch durchgesetzt, daher kann eine handgeschriebene oder generierte Datei, die diesem Schema entspricht, gefahrlos importiert werden.
Aufbau
Eine Definition ist ein JSON-Objekt mit diesen Membern:
| Feld | Typ | Pflicht | Hinweise |
|---|---|---|---|
logicalName | string | ja | Mit Herausgeberpräfix, Snake-Case in Kleinbuchstaben (z. B. par_invoice). |
displayName | string | ja | Beschriftung im Singular (z. B. Invoice). |
displayCollectionName | string | ja | Beschriftung im Plural (z. B. Invoices). |
icon | string | nein | Symbolschlüssel aus dem kuratierten Symbolsatz der Plattform, angezeigt neben der Tabelle in der Navigation einer App (§4.4). Weglassen für das Standardsymbol. |
publisherPrefix | string | ja | Kurzes Präfix in Kleinbuchstaben (z. B. par). |
primaryField | string | ja | Logischer Name des Primärfelds (Namensfelds); muss in fields vorkommen. |
primaryImage | string | nein | Logischer Name des Datensatzbilds (§6.16) – des Bilds, das in Rastern und Nachschlagefeldern neben einem Datensatz angezeigt wird. Muss ein Feld vom Typ Image in fields benennen. Weglassen für eine Tabelle ohne Bild. |
secondaryField | string | null | nein | Logischer Name des Felds, das als Sekundärtext des Datensatzes angezeigt wird – die kleinere zweite Zeile unter seinem Namen überall dort, wo Datensätze ohne Raster aufgelistet werden, etwa in einem Nachschlagebereich (§6.13.5). Muss ein Feld in fields benennen, das weder das Primärfeld noch eine Datei-/Bildspalte ist. |
hierarchyParentField | string | null | nein | Logischer Name des Nachschlagefelds, das diese Tabelle zu einer Hierarchie macht – des Felds, das das übergeordnete Element jedes Datensatzes enthält und auf dieselbe Tabelle zurückverweist (die übergeordnete Kategorie einer Kategorie, die übergeordnete Einheit einer Einheit). Muss ein Feld in fields vom Typ Lookup benennen, dessen targetEntity diese Tabelle ist. Es gewährt nichts und erzwingt nichts: Die Tabelle wird weiterhin als Raster gerendert, bis eine Ansicht ihre Datensätze verschachtelt. Es dient dazu, dass eine Hierarchie eine Eigenschaft der Daten ist und nicht einer einzelnen Ansicht – die oberste Ebene einer Baumansicht verwendet es als Standard für ihre Verknüpfung zu untergeordneten Elementen. |
personField | string | null | nein |
constraints | array | nein | Regeln, die die Datenbank durchsetzt, nicht die Anwendung (siehe unten) – eine eindeutige Spalte oder ein Zeitraum, der sich nicht mit einem anderen im selben Geltungsbereich überschneiden darf. |
fields | array | ja | Eine oder mehrere Felddefinitionen (siehe unten). |
relationships | array | nein | 1:n-Beziehungen, die auf Nachschlagefeldern beruhen. |
isAuditEnabled | boolean | nein | Aktiviert die Änderungsüberwachung für die Tabelle (Abschnitt 6.5). Standard ist false. |
states | array | nein | Die Status, in denen sich ein Datensatz dieser Tabelle befinden kann (§6.14) – sein Lebenszyklus. Siehe unten. Fehlt in einem Dokument, das geschrieben wurde, bevor es Status gab; daran unterscheidet ein Importer es von einer Tabelle ohne Status. |
reasons | array | nein | Die Gründe, von denen jeder zu genau einem Status gehört. Siehe unten. |
statusValueBase | integer | nein | Die Untergrenze des Bereichs, aus dem die hinzugefügten Status- und Grundoptionen dieser Tabelle nummeriert werden – die Optionswert-Basis des besitzenden Herausgebers (§6.14). Die vorbelegten Werte Aktiv/Inaktiv liegen außerhalb davon bei 0 und 1: Jede Tabelle hat sie, und sie bedeuten überall dasselbe, brauchen also keinen Namensraum. |
nextStatusValue | integer | nein | Der Höchststand der Statuswerte der Tabelle – der nächste Wert, den sie vergibt. Gespeichert statt abgeleitet aus den Optionen, aus demselben Grund wie bei einem Optionssatz: Die Nummer einer entfernten Option ist stillgelegt, und eine erneute Ableitung würde sie an die nächste neue Option vergeben, während Datensätze sie womöglich noch enthalten. |
Feld
| Feld | Typ | Pflicht | Hinweise |
|---|---|---|---|
logicalName | string | ja | Snake-Case in Kleinbuchstaben. |
displayName | string | ja | |
dataType | string | ja | Einer von Text, MultilineText, Integer, Decimal, Boolean, DateTime, Lookup, PolymorphicLookup, File, Image, OptionSet, State, Reason. |
isRequired | boolean | nein | |
isPrimary | boolean | nein | true für das eine Primärfeld. |
maxLength | integer | nein | Für Text (1–4000). |
precision, scale | integer | nein | Für Decimal (scale ≤ precision ≤ 38). |
targetEntity | string | bedingt | Pflicht, wenn dataType Lookup ist; der logische Name der referenzierten Tabelle. |
targetEntities | string[] | bedingt | Pflicht, wenn dataType PolymorphicLookup ist; die Tabellen, auf die ein Wert zeigen darf, in der Reihenfolge der Auswahl. Mindestens zwei, keine doppelt. |
isAuditExcluded | boolean | nein | Nimmt das Feld von der Änderungsüberwachung aus, wenn die Tabelle überwacht wird (Abschnitt 6.5). Standard ist false. |
PolymorphicLookup-Felder werden als zwei Spalten angelegt: die eigeneuuid-Spalte des Felds mit der ID des Datensatzes und eine benachbartetext-Spalte<column>_typemit dem logischen Namen der Zieltabelle. Keine von beiden trägt einen Fremdschlüssel – eine einzelne Spalte kann nicht auf mehrere Tabellen verweisen –, die Integrität liegt also beim Schreibpfad: Er prüft, ob das Ziel existiert, und ein danach gelöschtes Ziel wird als nicht aufgelöster Verweis gelesen.Auf dem Transportweg reisen die beiden Hälften als ein Skalar,
<table>:<id>, sodass dievalueseines Datensatzes eine flache Zuordnung von logischem Feldnamen zu Skalar bleiben und die Diskriminatorspalte nie als Feld auftaucht. Das ist die bestehende Konvention der Plattform für einen selbstbeschreibenden Verweis (recordfile:<id>,webresource:par_/app.js).Gespeichert wird der logische Name der Tabelle und keine Zahl, denn ein numerischer Diskriminator wäre ein Index in
targetEntities– eine Umsortierung der Ziele im Designer würde also stillschweigend jede gespeicherte Zeile auf eine andere Tabelle umleiten. Die Besitzer-Spalten eines Datensatzes verwenden dagegen eine numerische Aufzählung, was konsistent und kein Widerspruch ist: Ihre fünf Werte sind im Code festgelegte Plattformkonzepte, keine Liste, die ein Ersteller bearbeitet.Jeder Eintrag in
targetEntitieserhält seine eigene zugrunde liegende Beziehung; dadurch bietet das Formular der referenzierten Tabelle für diese Datensätze eine Registerkarte Verwandt an.
File-/Image-Felder werden als Textspalte angelegt, die einen Verweis mit Schema-Präfix enthält –recordfile:<id>für eine Datei im eigenen Schema der Umgebung oderobjectstore:<key>für eine, die groß genug war, um in den Object Storage ausgelagert zu werden. Die Bytes werden an den eigenen Dateiendpunkt des Felds hochgeladen statt über die Datensatz-API geschrieben und unter der Autorisierung des Datensatzes selbst ausgeliefert. Welcher der beiden Speicher eine Datei enthält, entscheidet ihre Größe und nichts, was ein Ersteller einstellt.
Beziehung
| Feld | Typ | Pflicht | Hinweise |
|---|---|---|---|
logicalName | string | ja | Snake-Case in Kleinbuchstaben. |
type | string | ja | OneToMany. |
referencedEntity | string | ja | Die „1“-Seite. |
referencingEntity | string | ja | Die „n“-Seite (trägt das Nachschlagefeld). |
lookupField | string | ja | Das Nachschlagefeld auf der referenzierenden Tabelle, auf dem die Beziehung beruht. |
Status und Grund
Ein Status gibt an, wo sich ein Datensatz in seinem Lebenszyklus befindet; ein Grund sagt, in welcher Ausprägung dieses Status er sich befindet. Sie reisen mit der Tabelle, weil sie Teil dessen sind, was der Ersteller entworfen hat.
🔴 Sie sind kein Optionssatz. Active auf einer Rechnung und Active auf einem Kontakt sind
nicht dieselbe Auswahl, und sie als eine gemeinsame Liste zu modellieren, würde bedeuten, dass das
Umbenennen des einen auch das andere umbenennt.
Status:
| Feld | Typ | Pflicht | Hinweise |
|---|---|---|---|
value | integer | ja | Der gespeicherte Wert, bei einem Import beibehalten: Die Datensätze der Quellumgebung enthalten ihn bereits, also müssen Entwicklung und Test darin übereinstimmen, was er bedeutet. |
displayName | string | ja | Die Beschriftung. |
color | string | nein | Die Farbe, in der sein Badge gezeichnet wird. |
icon | string | nein | Das Symbol auf seinem Badge. |
defaultReason | integer | nein | Welchen der Gründe dieses Status ein Datensatz erhält, der in ihn wechselt. Fehlt die Angabe, ist es der erste – deshalb ist die Einstellung optional. |
Grund:
| Feld | Typ | Pflicht | Hinweise |
|---|---|---|---|
state | integer | ja | Der value des Status, zu dem dieser Grund gehört. Über den Wert statt über die Zeilen-ID verknüpft, denn der Wert ist das, was ein Datensatz speichert und was einen Export übersteht. |
value | integer | ja | Der gespeicherte Wert. |
displayName | string | ja | Die Beschriftung. |
color · icon | string | nein | Wie bei einem Status. |
Genau um diese Zugehörigkeit geht es: Won und Lost sind beides Gründe dafür, dass ein
Geschäft inaktiv ist, und sie anzubieten, solange es aktiv ist, wäre unsinnig.
Beispiel
{
"logicalName": "par_invoice",
"displayName": "Invoice",
"displayCollectionName": "Invoices",
"publisherPrefix": "par",
"primaryField": "par_name",
"fields": [
{ "logicalName": "par_name", "displayName": "Name", "dataType": "Text", "isPrimary": true, "isRequired": true, "maxLength": 200 },
{ "logicalName": "par_total", "displayName": "Total", "dataType": "Decimal", "precision": 18, "scale": 2 },
{ "logicalName": "par_account", "displayName": "Account", "dataType": "Lookup", "targetEntity": "par_account" }
],
"relationships": [
{ "logicalName": "par_account_invoices", "type": "OneToMany", "referencedEntity": "par_account", "referencingEntity": "par_invoice", "lookupField": "par_account" }
]
}
Versionierung
Ältere Schemaversionen werden neben neuen aufbewahrt, sodass bereits bereitgestellte Lösungen
weiterhin validiert werden, nachdem sich das Schema weiterentwickelt hat. Ist eine
inkompatible Änderung nötig, wird eine neue Version (model-driven-entity@2) veröffentlicht,
statt diese zu bearbeiten.