Zum Inhalt springen

Modelle und Metadaten

Kurzantwort: Jedes CDMS-Modell existiert zur Laufzeit in vier Ausprägungen – Entity (Persistenz), DTO (Antwort), CreatePayload/UpdatePayload (Eingabe) – plus einem MetaService, der Felder, Typen, Beziehungen, Regeln und Rollen beschreibt. Die Einordnung eines Modells als System, Tenant oder User entscheidet über Speicherort und Sichtbarkeit.


flowchart LR
    P["{Model}CreatePayload<br/>{Model}UpdatePayload"] -->|Payload2DtoMapper| D["{Model}Dto"]
    D -->|Dto2EntityMapper| E["{Model}Entity"]
    E -->|Entity2DtoMapper| D
    M["{Model}MetaService<br/>Felder, Regeln, Rollen"] -.beschreibt.-> D
    M -.beschreibt.-> E
KlasseRolle
{Model}EntityJPA-Entity, Persistenzmodell
{Model}DtoAntwortmodell, trägt _reference gegen Zyklen
{Model}CreatePayloadEingabemodell für Create, ohne id
{Model}UpdatePayloadEingabemodell für Update, id ist Pflicht
{Model}MetaServiceModelMetaInterface: MetaClassInfo + MetaFieldInfo je Feld

Drei @MappedSuperclass-Basisklassen entscheiden über Speicherort und Filter:

BasisklasseSpeicherortZusatzfeldAutomatischer Filter
AbstractSystemModelSystem-Datenbankkeiner
AbstractTenantModelTenant-DatenbankMandant (über die Datenbankwahl)
AbstractUserModelTenant-Datenbank_userId (not null, 36 Zeichen)zusätzlich _userId = aktueller Benutzer

Alle drei erben von AbstractEntityModel mit _createdOn, _updatedOn, einer transienten _internalId und einem ID-basierten equals. Die ID ist immer eine UUID, in der Datenbank als BINARY(16).

User ist keine eigene Datenbank. Die Benutzerebene ist eine Verfeinerung innerhalb der Mandantendatenbank; der Owner-Filter läuft im System-Layer.

FeldBedeutungWer setzt es
idUUID des ObjektsJPA beim Anlegen
_createdOnAnlagezeitpunktSystem-Layer beim Create
_updatedOnÄnderungszeitpunktPersistenz
_userIdBesitzer (nur AbstractUserModel)System-Layer aus dem RequestContext
_MODELTYPEDiskriminator bei VererbungPersistenz/JPA
_referenceZyklusschutz während der Rekursion (nur DTO/Payload, nicht persistiert)System-Layer

Felder, die mit _ beginnen, überspringt die Rekursion beim Schreiben – der Client kann sie nicht setzen.

FeldBedeutung
instanceablefalse bei abstrakten Modellen
auditedModell führt Historie (Envers) und Dateiversionen
environmentSYSTEM, TENANT oder USER
name, packagePath, filePathNamen und Pfade, filePath auch für die Dateiablage
dtoClassReference, entityClassReference, createPayloadClassReference, updatePayloadClassReferencedie vier Klassen
createRole, readRole, updateRole, deleteRole, downloadRole, historyRole, rollbackRolebenötigte Rollen je Operation
dtoChildReference, entityChildReference, payloadCreateChildReference, payloadUpdateChildReferencekonkrete Untertypen bei Vererbung
FeldBedeutung
fieldNameFeldname
relation, listBeziehung? Liste oder Einzelreferenz?
referenceTypeONETOONE, ONETOMANY, MANYTOONE, MANYTOMANY
backReferenceFieldNameFeldname der Gegenseite
simpleClassReference / dtoClassReference / entityClassReferenceZieltypen
recursiveCreate, recursiveUpdate, recursiveDeleteerlaubte Rekursion über diese Beziehung
createRole, readRole, updateRole, deleteRoleFeldrollen
hooksFeld-Hooks
rulesMetaFieldRules – Validierungsregeln

notNullOnCreate, notNullOnUpdate, notEmpty, fieldLength, numberMin, numberMax, stringPattern, unique, updatable, noCreate, defaultValue sowie je Regel ein RuleScope (ALWAYS, CREATE, UPDATE). Details in Validierung.

Diese Annotationen beschreiben ein Modell in der Quelle bzw. im generierten Code (cdms-commons):

AnnotationZielWirkung
@DataObject(apiPath, environment, type, root)KlasseModell ist ein Wurzelobjekt; API-Pfad, SYSTEM/TENANT/USER, DATA/FILE
@ExposeApi(create, read, update, patch, delete, query, download, history, rollback)Klassewelche Endpunkte erzeugt werden
@AccessByRole(onCreate, onRead, onUpdate, onDelete, onDownload, onHistory, onRollback)Klassebenötigte Rollen
@AccessByAttribute(attribute, field)KlasseProfilattribut muss Feldinhalt entsprechen
@AccessFieldByRole(onCreate, onRead, onUpdate, onDelete)FeldFeldrollen
@History(rollback)KlasseAuditing, optional mit Rollback
@SingletonKlassegenau ein Objekt pro Scope
@Reference(relation, backReferenceFieldName, recursive)FeldBeziehungstyp, Gegenseite, erlaubte Rekursion
@Children(classPath, name) / @ChildrensKlassekonkrete Untertypen
@UniqueFeldeindeutiger Wert
@DefaultValue(value)FeldVorbelegung beim Anlegen
@NotNull(onCreate, onUpdate, notEmpty)FeldPflichtfeld je Operation
@FieldSize(size)Feldmaximale Stringlänge
@NumberMin(value, onCreate, onUpdate) / @NumberMax(...)FeldZahlengrenzen
@StringPattern(value, onCreate, onUpdate)FeldRegex
@LargeTextDataFeldgroßer Text (CLOB)
@DecryptionFeldEntschlüsselung in der Datenbank
@Hook({...})Klasse/FeldHook-Implementierungen

Der Generator bildet CDMS-Feldtypen so auf Java ab:

CDMS-FeldtypJava
StringField, TextField, TextAreaField, PasswordField, EmailField, UrlFieldString
IntegerField, IntFieldInteger
LongFieldLong
DoubleField, NumberFieldDouble
FloatFieldFloat
BooleanField, CheckboxFieldBoolean
UUIDFieldUUID
DateFieldLocalDate
TimeFieldLocalTime
DateTimeField, TimestampFieldLocalDateTime
BigDecimalField, DecimalFieldBigDecimal
ByteArrayField, BinaryFieldbyte[]
PrimitiveFieldüber das Attribut type im YAML

Ein Modell mit ClassType.FILE implementiert FileInterface: name, mimeType, fileSize, fileVersion, fileId. Die Bytes liegen im Storage, die Metadaten in der Datenbank – siehe Dateien und Storage.

Ein @Singleton-Modell hat pro Scope genau ein Objekt. Konsequenzen:

  • kein Query-Endpunkt, keine ID in der URL
  • read liefert das eine Objekt, create schlägt fehl, wenn es schon existiert (object-already-exists|use-update)
  • update/patch setzen die intern ermittelte ID selbst ein
  • Typische Fälle: Tenant-Einstellungen, Benutzereinstellungen, Systemkonfiguration