In diesem Blogpost werfen wir einen Blick auf die Verwendung von JSON Schema bei der Digitalisierung von Prozessen mit BPMN. Dabei werden verschiedene Anwendungsszenarien vorgestellt - von der Modellierung von Formularen bei Benutzeraufgaben bis hin zur Validierung und Serialisierung von Daten im Backend. Zudem werden Implementierungsansätze mit Camunda 7 betrachtet.
JSON ist ein einfaches, textbasiertes Datenformat, das im Web für viele Datenübertragungen genutzt wird. JSON Schema ist ein dazugehöriger IETF-Standard (Internetstandard), der es ermöglicht, JSON-Daten zu beschreiben und zu validieren. Er bietet verschiedene Vorteile, u.A.:
Nehmen wir das folgende JSON, das aus den Werten Vorname, Nachname und Alter besteht:
{ "firstName": "John", "lastName": "Doe", "age": 21 }
Das dazugehörige Schema kann unterschiedliche Ausprägungen haben. Es kann sich bspw. um eine Person mit den folgenden Eigenschaften handeln:
Ein passendes JSON Schema könnte wie folgt definiert werden:
{ "title": "Person", "type": "object", "required": [ "firstName", "lastName", "age" ], "properties": { "firstName": { "type": "string", "description": "The person's first name." }, "lastName": { "type": "string", "description": "The person's last name." }, "height": { "type": "integer", "description": "The person's height in cm." }, "age": { "description": "Age in years which must be equal to or greater than zero.", "type": "integer", "minimum": 0 } } }
Es handelt sich um eine Person
****mit dem type
object
. Unter properties
werden die Attribute des Objektes definiert. Ein Attribut besteht dabei immer aus einem type
und kann zusätzlich mit einer Beschreibung (description
) versehen werden.
JSON Schema bietet zahlreiche verschiedene Arten der Validierung an. Attribute vom Typ integer
können bspw. mit einer Eigenschaft minimium
versehen werden. Bei einer Person ist dies hilfreich, da ein negatives Alter nicht möglich ist.
Auf Ebene der Person ist zusätzlich ein required
Array definiert. Dieses enthält Eigenschaften der Person, die zwingend vorhanden sein müssen. Die height
ist es nicht, weshalb das zuvor definierte JSON gültig ist.
Mehr über JSON Schema kannst Du unter https://json-schema.org/ erfahren. Dort findest Du auch Tutorials und weitere Links.
Doch was nutzt uns die Definition eines Schemas für die Modellierung von Formularen? Die Idee dahinter ist einfach: Wir können ein Schema nutzen, um Daten im Frontend zu visualisieren und das gleiche Schema verwenden, um diese im Backend zu validieren.
Es gibt zahlreiche Bibliotheken für unterschiedliche Frameworks, die aus einem JSON Schema ein Formular zur Laufzeit generieren.
Um das Beispiel einfach zu halten, verwenden wir im folgenden die Bibliothek Vuetify JSON Schema Form. Das Schema entspricht, mit ein paar kleineren Anpassungen, dem Personenschema aus dem vorangegangen Abschnitt.
{ "type": "object", "required": [ "firstName", "lastName", "age" ], "properties": { "firstName": { "type": "string", "title": "Firstname", "description": "The person's first name." }, "lastName": { "type": "string", "title": "Lastname", "description": "The person's last name." }, "address": { "type": "string", "title": "Address", "x-display": "textarea", "description": "The person's address." }, "height": { "type": "integer", "title": "Height", "description": "The person's height.", "x-display": "slider", "minimum": 30, "maximum": 240 }, "age": { "type": "integer", "title": "Age", "description": "The person's age.", "minimum": 0 } } }
Der Unterschied zum JSON Schema der Person sind die title
und x-display
Attribute, die von der Library beim Rendering des Formulars verwendet werden. Als Ergebnis liefert die Library folgendes Formular:
Es gibt noch zahlreiche Möglichkeiten, um Anpassungen an der UI vorzunehmen oder eigene Input Controls bereitzustellen. Mehr dazu kann in der Dokumentation der Library nachgelesen werden: https://koumoul-dev.github.io/vuetify-jsonschema-form/latest/
Dieses Schema kann anschließend mit einer Benutzeraufgabe verknüpft werden. In DigiWF bei der Landeshauptstadt München haben wir dafür eine JSON Schema Registry implementiert (https://github.com/it-at-m/digiwf-core/tree/dev/digiwf-schema-registry) und verknüpfen das passende Schema über eine Input-Variable in der Benutzeraufgabe.
Das Frontend lädt sich dann das entsprechende Schema und zeigt die Daten dazu passend an. Für die Modellierung von Formularen haben wir in DigiWF einen Drag&Drop-Formular-Builder erstellt: https://github.com/it-at-m/digiwf-core/tree/dev/digiwf-apps/packages/components/digiwf-form-builder Bei Miragon haben wir zudem ein VS-Code-Plugin bereitgestellt, das die Modellierung von Formularen in der IDE ermöglicht: https://github.com/Miragon/vs-code-vuetify-jsonschema-builder
Doch wie können wir bei der Ausführung von Prozessen sicherstellen, dass ein Benutzer nur auf bestimmte Daten Lese- und Schreibzugriff hat?
Um zu verstehen wie das Problem gelöst werden kann, schauen wir uns zunächst einmal das Zusammenspiel der einzelnen Komponenten in DigiWF an, wenn ein Benutzer eine Aufgabe öffnet.
Wenn ein Benutzer eine Aufgabe in der Aufgabenliste öffnet wird ein Request an die Engine gesendet. Diese lädt sich anhand des Input Parameters app_schema_task_key
das entsprechende Schema aus der Registry. Anhand des Schemas werden die Daten aus der Prozessinstanz extrahiert und schlussendlich an die Tasklist zurückgesendet. Es wird somit über die Definition des Schemas eingeschränkt, auf welche Daten ein Benutzer Zugriff hat. Dabei wird im Backend das gleiche Schema verwendet wie im Frontend.
Ein weiterer Anwendungsfall, der etwas komplexer ist, ist das Abschließen von Aufgaben, das im folgenden Schaubild visualisiert ist:
Wenn ein Benutzer eine Aufgabe abschließt, werden im nächsten Schritt die Daten anhand des Schemas im Backend validiert und gefiltert. Damit wird verhindert, dass über die API Daten eingespielt werden, die ein Benutzer nicht schreiben darf oder die einen falschen Datentyp haben.
Danach werden die bisherigen Daten aus der Engine geladen. Der Grund dafür ist, dass es im JSON Schema verschachtelte Objektstrukturen geben kann:
{ "title": "Person", "type": "object", "required": [ "firstName", "lastName", "age" ], "properties": { "firstName": { "type": "string", "description": "The person's first name." }, "lastName": { "type": "string", "description": "The person's last name." }, "height": { "type": "integer", "description": "The person's height in cm." }, "age": { "description": "Age in years which must be equal to or greater than zero.", "type": "integer", "minimum": 0 }, "home": { "type": "object", "description": "The person's home", "properties": { "address": { "type": "string", "description": "The address of their home." }, "size": { "type": "integer", "readOnly": true, "description": "The size of their home in square meters." } } } } }
Zur einer Person könnte bspw. ein Objekt Haus gehören, wobei die Eigenschaft size
readOnly
ist und durch den Benutzer nicht verändert werden kann. In der Engine werden die Daten jedoch als JSON Objekte gespeichert. Dies bedeutet, dass sie nur als ganzes gespeichert werden können. Deshalb werden die bestehenden Daten geladen und mit den Änderungen des Benutzers zusammengefügt. Erst nach diesem Schritt können die Daten gespeichert und zusammen mit der Aufgabe abgeschlossen werden. In DigiWF haben wir hierfür eine eigene Bibliothek geschrieben, die bestimmten Funktionalitäten dafür kapselt: https://github.com/it-at-m/digiwf-core/tree/dev/digiwf-libs/digiwf-json-serialization
In diesem Blogpost haben wir einen kurzen Einblick in die JSON Schema Technologie erhalten und Implementierungsansätze kennengelernt, wie wir diese bei der Digitalisierung von Prozessen mit Camunda nutzen können. Im nächsten Blogpost schauen wir uns einen weiteren Anwendungsfall an - JSON Schema als Datenbeschreibung eines Prozesses, um die Nutzung von wiederverwendbaren Prozessbausteinen zu vereinfachen.
Rechtliches