Da sogenannte Actions die wichtigste Art der Verwendung vieler App-Store-Module sind, lohnt es sich, etwas Zeit und Gedanken in die Gestaltung guter Actions zu investieren. Hier sind elf Best Practices, die ich für jeden habe, der eine Java(Script)-Action schreibt, entweder zur Verwendung in einem Projekt oder als Teil eines wiederverwendbaren Moduls.
1. Verwenden Sie beschreibende Parameternamen und fügen Sie ihnen einen Unterstrich hinzu
Der Name eines Parameters sollte beschreibend und nicht redundant sein. Das ist ein allgemeiner Ratschlag zur Namensgebung, der auch hier gilt. Vermeiden Sie also Namen wie Objekt, Parameter, Daten. Beachten Sie, dass Action-Parameter nicht nur in Mendix beim Aufruf der Action verwendet werden, sondern auch im Java(Script)-Code als Variablennamen. Das verursacht ein kleines Problem, wenn der Parametername ein reserviertes Wort in der jeweiligen Sprache ist, z.B. Object. Um dies zu umgehen, hat Mendix den Variablennamen im Code mit Parameter1 etc. ergänzt. Dies wurde in neueren Mendix-Versionen überarbeitet, was das Problem tatsächlich verschlimmert. Nun kann es sein, dass derselbe Action-Code auf einer älteren Mendix-Version gut funktioniert und auf einer neueren Mx-Version nicht kompiliert werden kann, weil die Variablennamen auf unterschiedliche Weise generiert werden.
Durch die Verwendung eines Unterstrichs als Suffix können alle Konflikte mit reservierten Wörtern, die mir bekannt sind, beseitigt werden und ist eigentlich nicht einmal sichtbar, wenn die Action aufgerufen wird, wie im Screenshot unten zu sehen ist. Perfekt!
2. Fügen Sie eine neue Action hinzu, anstatt Änderungen an der Signatur vorzunehmen.
Wir haben es alle schon erlebt. Selbst die am besten durchdachte Action muss geändert werden. Einige dieser Änderungen werden unweigerlich eine Änderung der Signatur mit sich bringen, z. B. weil ein neuer Parameter hinzugefügt oder ein Rückgabetyp geändert wird. Dies ist sehr störend, da alle entsprechenden Action-Aufrufe im gesamten Projekt aktualisiert werden müssen.
Die alte Action sollte intern die neue aufrufen und einen Standardwert für die neuen Parameter übergeben oder einen Rückgabewert ignorieren, falls ein solcher hinzugefügt wurde. Achten Sie darauf, beiden Actions einen logischen Namen zu geben, vielleicht sollte die alte Action irgendwo das Wort "deprecated" enthalten. Eine ergänzende Vorgehensweise, um zu viele Signaturänderungen zu vermeiden, wird im Folgenden beschrieben.
3. Bündeln Sie Flags, Enums und Integer-Einstellungen in einem einzigen "Options"-Objekt.
Oftmals hat eine Action mehrere Flags oder verschiedene Einstellungen, die angepasst werden können und daher irgendwie offengelegt werden müssen. Es ist am besten, alle diese Parameter in einem einzigen Objekt zu bündeln, das oft Optionen, Einstellungen oder Konfiguration genannt wird.
Durch die Verwendung eines "Options"-Objekts können mehrere Probleme auf einmal angegangen werden: 1) das Hinzufügen einer neuen Einstellung ändert nicht die Signatur; 2) Standardwerte können im Objekt definiert werden und verschmutzen nicht den Konstantenbereich; 3) die Dokumentation für jedes Flag hat einen eindeutigen Platz, ohne den Actions-Dialog zu überfrachten; 4) die Liste der Action-Parameter kann kürzer sein, wodurch der Aufruf der Action schneller und sauberer wird (denken Sie daran, dass jeder Parameter explizit gesetzt werden muss).
Ein Beispiel für ein "Options"-Objekt aus dem App-Store-Modul "Parallel Execute".
4. Verwenden Sie immer eine sogenannte Fassade, anstatt Bibliotheken von Drittanbietern direkt aufzurufen.
Dies ist für alle, die Drittanbieter-Abhängigkeiten in Form von jar-Dateien oder Javascript-Modulen verwenden. Ich habe einen separaten ausführlichen Blog-Beitrag mit den Best Practices dafür geschrieben. Durch die Verwendung einer Fassade vermeiden wir eine harte Kopplung und machen es möglich, die Bibliothek ohne großen Aufwand auszutauschen. Außerdem ist es am besten, ein sprachspezifisches Abhängigkeitsmanagement- und Build-Tool wie maven oder gradle für Java und npm oder webpack für Javascript zu verwenden. Diese Werkzeuge bieten eine einfache Verwaltung von transitiven Abhängigkeiten, Testintegration in den Build-Prozess und Paketierung für die gewünschte Plattform.
5. Den Benutzer beim Aufbau von Objekthierarchien mit einem Builder-Pattern anleiten
In manchen Fällen sind Argumente für eine Action nicht eine einfache Zahl oder ein Objekt mit ein paar Parametern. Stattdessen wird eine ganze Hierarchie von Objekten benötigt, die auf eine bestimmte, von der Action erwartete Weise verknüpft sein muss. Dies ist zur Entwurfszeit unmöglich zu erzwingen, daher ist ein geschickter Weg erforderlich, um den Benutzer beim Aufbau der richtigen Hierarchie und beim Setzen der richtigen Assoziationen zu führen.
Bei mittelgroßen Hierarchien, d. h. ein oder zwei Assoziationen, bevorzuge ich die Verwendung eines Builder-Patterns, um den Benutzer bei der Vorbereitung der Daten zu unterstützen. Das Builder-Pattern erlaubt es, komplexe Hierarchien mit einfachen Schritten aufzubauen, einen Schritt nach dem anderen. Zusätzlich wird jeder Schritt durch Typen eingeschränkt, die oft nur von anderen Schritten erhältlich sind. In einer idealen Welt wäre es möglich, die manuelle Instanziierung einiger Builder-Objekte vollständig zu verhindern und so den Benutzer zu zwingen, sie mithilfe einer Action zu erstellen.
Beispiel für das Erstellungsmuster, das in meinem Modul "Web-Push-Benachrichtigungen" verwendet wird. In Schritt1 wird ein Notification-Objekt mithilfe einer Java-Action erstellt. Dann wird in Schritt2 ein NotificationAction-Objekt erstellt und mit einer Notification verknüpft, aber anstatt dies direkt zu tun, wird wieder eine Java-Action verwendet, die ein Notification-Objekt als Parameter benötigt. Dies macht es unmöglich, die Reihenfolge der Schritte zu verwechseln und Schritt2 vor Schritt1 aufzurufen oder das Setzen der Zuordnung zu vergessen.
6. Verwenden Sie die spezifischsten und einschränkendsten Typen wie möglich.
Dies ist ein weiterer allgemeiner Ratschlag, der auch für Actions gilt und eng mit der vorherigen Best Practice zusammenhängt. Verwenden Sie anstelle von Strings "Ja" oder "Nein" boolesche Werte. Verwenden Sie Enums und Datumsangaben anstelle von Strings, wo dies möglich ist. Vermeiden Sie das Überladen von Nullen oder anderen speziellen Werten, um Informationen zu übermitteln, verwenden Sie stattdessen spezifische Typen oder Enums dafür. Wenn die Antwort z. B. eine Fehlermeldung oder Null ist (schreckliche Idee, siehe 11), verwenden Sie stattdessen ein Objekt mit einem booleschen "HasError" und einer Fehlermeldung.
7. Verwenden Sie nur Code für Dinge, die nicht nativ in Mendix gemacht werden können.
Wenn etwas in Mendix gemacht werden kann, dann machen Sie es in Mendix. Besonders bei Abrufen und/oder Ändern von Objekten. Es ist viel einfacher und sicherer, einen Flow aus der Mitte der Action aufzurufen, um einen Retrieve nativ in Mendix durchzuführen, als den XPath und Retrieve mit Code zu konstruieren.
Wenn Sie Objektattribute ändern, denken Sie daran, dass das Umbenennen des Attributs nicht automatisch die bestehenden Aufrufe von Getter/Setter-Methoden auf dem jeweiligen Proxy umbenennt. Auch hier sollten Sie also nur dann Code verwenden, wenn es keine Alternative gibt, dasselbe nativ in Mendix zu tun.
8. Fügen Sie keine unnötige Kopplung zu Mendix-Laufzeit- und Kernmethoden hinzu.
Vermeiden Sie die Kopplung von Code mit Mendix-APIs, insbesondere bei Java-Actions. Mendix nimmt häufig Änderungen an den Laufzeit- und Core-Methoden vor, siehe nur diese riesige Liste in der Mendix 8 Version. Um also Kopfschmerzen zu vermeiden, sollten Sie sich nicht auf Mendix-Laufzeitbibliotheken oder Core-Methoden verlassen, wenn es nicht unbedingt notwendig ist. Es ist klar, dass eine gewisse Kopplung unvermeidbar ist - das Ziel ist es, diese auf das absolute Minimum zu beschränken.
Wenn Sie die anderen Best Practices befolgen, sollte dies eine Selbstverständlichkeit sein. Wenn Sie sich irgendwann dazu entschließen, diesen Teil aus Mendix auszulagern und auf einen separaten Server zu verlagern, dann wird es mit weniger Abhängigkeiten einfacher, diesen Teil der Funktionalität zu migrieren.
9. Überprüfen Sie die Anzahl und die Typen der Parameter für Microflow-Parameter.
Dies gilt nur bei der Verwendung von Microflow als Parameter. Leider erlaubt Mendix immer noch nicht, die Parameteranzahl und -typen zu beschränken, wenn sie in einer Action verwendet werden. Bis das implementiert ist, ist die nächstbeste Möglichkeit, die Eingabe- und Ausgabetypen zur Laufzeit zu überprüfen. Dies ist nicht trivial, bitte schauen Sie sich das Parallel Execute Modul für eine gute Implementierung an, die auch Vererbung abdeckt.
10. Verwenden Sie json als Vermittler für die Kommunikation mit Actions.
Das Builder-Pattern wurde bereits als Möglichkeit für den Umgang mit mittleren Objekthierarchien erwähnt. Aber was ist mit großen Objekthierarchien? In diesem Fall ist es am besten, auf json zurückzugreifen. Dies gibt dem Benutzer die völlige Freiheit, sein eigenes Entity-Diagramm mit Hilfe eines Export/Import-Mappings auf/von json abzubilden. Mappings werden in Mendix nativ unterstützt, d.h. sie kommen mit Typsicherheit und erlauben das einfache Umbenennen von Entitäten, Assoziationen und Attributen. Dies im Gegensatz zu Code, bei dem eine solche Umbenennung einen Kompilierungsfehler verursachen würde.
Um es auf die nächste Stufe zu heben, schlage ich vor, ein JSON-Schema zu verwenden, um das eingehende/ausgehende json zu validieren. Ein kostenloser Bonus, den Sie durch die Verwendung von json erhalten, ist, dass es Actions einfacher zu testen macht. Sie müssen nicht für jeden Testfall eine Objekthierarchie aufbauen, sondern fügen einfach ein Test-Json ein.
11. Lassen Sie Exceptions propagieren, anstatt sie zu unterdrücken.
Wenn eine Exception innerhalb des Codes auftritt, lassen Sie sie am besten an Mendix weiterleiten. Dann kann der Aufrufer der Action entscheiden, wie er sie am besten behandelt. Es gibt selten einen guten Grund, Exceptions zu unterdrücken.
Manchmal kann es sinnvoll sein, eine Exception mit einer einfacheren Fehlermeldung erneut auszulösen und die eigentliche Meldung auf einer anderen Ebene als Fehler zu protokollieren. Zum Beispiel ist "NumberFormatException in Zeile 192*"* viel weniger informativ als "Betrag ist keine gültige Zahl".
"Ich hoffe, Sie hatten Spaß beim Lesen dieses Beitrags und dass er Ihnen hilft, bessere Mendix-Code-Actions zu entwerfen!''