Suche
Innovative Sprachfunktionen für Java – Teil 1

Manifold: Die Neuerfindung des Code-Generators

Scott McKinney

© Shutterstock / gonin

Mit diesem Artikel startet eine neue Serie zu Manifold. Manifold ist eine einzigartige Open-Source-Technologie, die man in jedem Java-Projekt verwenden kann, um innovative Sprachfunktionen wie typsichere Metaprogrammierung, Erweiterungsmethoden, Templating und strukturelle Typisierung nutzen zu können. Im ersten Teil geht es um die Neuerfindung von Code-Generatoren durch Manifold, nämlich Type Manifold.

☠ Death to Code Generators ☠

Obwohl Manifold eine Vielzahl von High-Level-Features anbietet, liegt der Fokus des Projektes darin, die Lücken zwischen Quellcode und Metadaten zu schließen. Softwareentwickler verwenden Code-Generatoren überwiegend, um die Lücke zu überbrücken, allerdings ist diese jahrzehntealte Methode dafür bekannt, Abläufe zu verlangsamen oder zu behindern und daher nicht gut für viele moderne Softwarearchitekturen geeignet. Wer in einem Projekt einen oder gar mehrere Code-Generatoren verwendet, kennt die Problematik vermutlich. Doch es gibt eine produktivere Alternative, die ich hier vorstellen möchte.

Die Trennung von Metadaten

Unser modernes Leben ist voll von strukturierten Informationen, auch bekannt als Metadaten. Sie sind überall und werden beinahe von allem produziert, was ein Netzteil hat. Infolgedessen ist die Softwareindustrie mittlerweile weniger auf Code fokussiert, stattdessen stehen Informationen im Zentrum. Trotz dieser Transformation hat sich die Art und Weise, wie unsere Software Metadaten verwendet, seit einem halben Jahrhundert nahezu nicht verändert. Ob JSON, XSD/XML, WSDL, CSV, DDL, SQL, JavaScript, XLS oder eine der vielen anderen Quellen für Metadaten, in den modernsten Sprachen (einschließlich Java) um diese mit dem eigenen Code zu verknüpfen:

../abc/Person.json

{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "Person",
  "type": "object",
  "properties": {
    "firstName": {
      "type": "string"
    },
    "lastName": {
      "type": "string"
    },
    "age": {
      "type": "integer",
      "minimum": 0
    }
  },
  "required": ["firstName", "lastName"]
}

Als Java-Entwickler wollen wir Metadaten auf typsichere Weise verwenden: Wir wollen die JSON-Schemadatei Person als Java-Klasse Person nutzen:

class Foo {
  private abc.Person person; // ERROR: Cannot resolve symbol 'abc.Person'
}

Natürlich hat der Java Compiler keine Ahnung, was er mit abc.Person machen soll. Daher greifen wir auf einen Code-Generator in einem separaten Schritt beim Build-Prozess zurück, um alle unsere JSON-Klassen vorher zu generieren. So kann der Compiler sie problemlos verwenden. Die Auswirkungen dieses Schrittes im Build-Prozess auf den Entwicklungszyklus reicht von leichter Irritation bis hin zur völligen Verwüstung, abhängig von der Veränderungsrate der Metadaten, der Anzahl und Größe der Metadatendateien, der Nutzungsdichte im Projekt, der Anzahl der Entwickler, etc.

Die folgenden Probleme können auftreten:

  • Veraltete generierte Klassen
  • Lange Build-Zeiten
  • Aufgeblähter Code, insbesondere bei großer Metadaten-Domäne
  • Änderungen an strukturierten Daten machen generierten Code nicht ungültig
  • Keine Unterstützung für inkrementelle Kompilierung – alles oder nichts
  • Es ist nicht möglich, von der Code-Referenz zum entsprechenden Element in den strukturierten Daten zu navigieren
  • Es ist nicht möglich, Code Usage von Elementen aus strukturierten Daten zu finden
  • Es ist nicht möglich, strukturierte Datenelemente umzubauen bzw. umzubenennen
  • Komplizierte Probleme mit benutzerdefinierten Class Loadern; generierte Klassen werden in einem separaten Loader geladen
  • Concurrency-Probleme mit dem gemeinsam genutzten Thread-Kontext-Loader
  • Generierter Code wird oft zwischengespeichert und freigegeben, was zu Problemen mit veraltetem Cache führt
  • Kunden müssen oft Metadaten ändern, was den Zugriff auf Code-Generatoren erfordert
API Summit 2018
Christian Schwendtner

GraphQL – A query language for your API

mit Christian Schwendtner (PROGRAMMIERFABRIK)

Das Manifold Framework

Das Manifold Framework stellt ein Umdenken in Sachen Code-Generatoren dar. Die vielen Nachteile, die oft mit ihnen verbunden sind, werden durch die direkte Integration mit dem Java Compiler über den javac Plug-in-Mechanismus vermieden.

Implementierer des Type Manifold APIs, genannt Type Manifolds, stellen eine Type Supplier ähnliche Beziehung mit dem Java Compiler her – das Manifold Framework hängt sich also in den Compiler ein. Wenn der Compiler auf Typnamen stößt, tragen die Type Manifolds zur Auflösung bei und erzeugen, je nach Bedarf, entsprechenden Code im Speicher. So kann der eigene Anwendungscode Quellen von Metadaten direkt namentlich als Java-Typen referenzieren, wodurch das vorherige Beispiel effektiv funktioniert:

class Foo {
  private abc.Person person; // OK 🙂
}

Man kann sich einen Type Manifold als eine neue Domäne von Typen vorstellen, auf die der Compiler zugreifen kann. Das Manifold Framework dient als ein Gateway zwischen javac und Type Manifolds, wodurch das Typsystem von Java effektiv um eine komplett neue Typendomäne erweitert wird. Eine beliebige Anzahl von Type Manifolds kann im Einklang miteinander arbeiten. Sie können sogar in so kooperieren, dass die von einem Type Manifold erzeugten Typen in den nächsten fließen können und so weiter. Dadurch wird eine Type-Building-Pipeline erstellt.

Wie das Diagramm veranschaulicht, trägt ein Type Manifold zur Definition von Typen in seiner Domäne bei. So erzeugt beispielsweise der JSON Type Manifold Typen, die in JSON-Dateien definiert sind. Ein Type Manifold kann auf unterschiedliche Weise zu einem Typ beitragen: Meistens registriert sich ein Type Manifold als „primärer“ Contributor und liefert den Hauptteil des Typs. Der JSON Type Manifold ist ein Primär-Contributor, da er die vollständige Typdefinition gemäß einer JSON-Schema-Datei oder JSON-Sample-Datei bereitstellt.

Alternativ kann ein Type Manifold ein „partieller“ oder „ergänzender“ Contributor sein. Zum Beispiel ist der Extension Type Manifold ein ergänzender Contributor, da er einen vorhandenen Typ um zusätzliche Methoden, Schnittstellen und andere Merkmale erweitert. Daher können sowohl der JSON Type Manifold als auch der Extension Type Manifold zu dem gleichen Typ beitragen, wobei der JSON Manifold den Hauptteil des Typs liefert und der Extension Type Manifold die Methoden sowie andere, von Erweiterungsklassen bereitgestellte, Merkmale enthält.

Insgesamt beseitigt diese Strategie viele Probleme, die herkömmliche Code-Generatoren und den Metadatenzugriff im Allgemeinen plagt. Im Wesentlichen definiert das Type Manifold API Code-Generatoren neu. Zu den Vorteilen gehören:

  • Zero Turnaround – Typsicherer Zugriff in Echtzeit auf Metadaten; Änderungen werden sofort vorgenommen, erkannt und genutzt
  • Leichtgewichtigkeit – Direkte Integration mit Standard-Java; erfordert keine speziellen Compiler, Annotationsprozessoren, Class Loader oder Laufzeitagenten
  • Effizient, dynamisch – Manifold erzeugt nur Typen, die vom Compiler benötigt werden
  • Einfaches, offenes API – Es können eigene Type Manifolds erstellt werden
  • Keinen zusätzlichen Schritt für Code-Generierung im Buid-Prozess – Code-Generatoren werden aus dem Build-Prozess eliminiert
  • IntelliJ IDEA – Umfassende IDE-Unterstützung mit Code-Vervollständigung, Navigation, Suche, Refactoring, Debugging usw.

Darüber hinaus vereinheitlicht das Type Manifold API die Code-Generator-Architektur, indem es die dringend benötigte Struktur und Konsistenz für Entwickler bereitstellt, die Code-Generatoren schreiben. Es macht Schluss mit den Code-Generator-Projekten, die nur ein einziger Entwickler versteht. Darüber hinaus muss man keine Zeit mehr in einmalige IDE-Integrationsprojekte investieren. Das Manifold-Plug-in für IntelliJ kümmert sich um alles, von der inkrementuellen Kompilierung über die Usage-Suche bis hin zum Refactoring.

Selbst wenn man bereits auf einen vorhandenen Code-Generator setzt, kann dieser Aufwand als Wrapped Type Manifold wiederverwertet werden. Der Wrapper kann die Produktion von Quellcode an das vorhandene Framework delegieren. In der Dokumentation gibt es weitere Informationen über das Implementieren von Type Manifolds.

Synergie

Der vielleicht größte Vorteil der Verwendung von Manifold ist die Synergie, die sich aus dessen Präsenz ergibt. Mit Manifold kann ein Entwickler Metadaten definieren und verwenden, die den Anforderungen eines Projekts am besten entsprechen, ohne sich um Build-Implikationen oder die IDE-Integration kümmern zu müssen. Er kann eine Metadatendatei erstellen, sie direkt als Typ verwenden, sie modifizieren und sofort auf die Änderungen im Code zugreifen. Es ist keine Kompilierung erforderlich, auch keine zusätzlichen Build-Schritte zum Aufrufen. Durch die umfassende IDE-Unterstützung kann man problemlos zu und von Metadatenelementen navigieren, Metadaten-Usages finden, ein Refactoring durchführen usw. Endlich sind Metadaten im Java-Entwicklungszyklus erstklassig repräsentiert! Auf der Homepage von Manifold gibt es dazu eine Demo.

Manifold: So wird es genutzt

Setup

Die Verwendung von Manifold im eigenen Java-Projekt ist denkbar einfach:

  • Man füge das/die Manifold JAR(s) zum Klassenpfad hinzu (sowie tools.jar, falls Java 8 genutzt wird)
  • Dann muss man nur noch -Xplugin:Manifold als Parameter zu javac hinzufügen (nur für die Kompilierung)

Das ist auch schon alles. Manifold unterstützt Java 8, Java 9 und Java 10 vollständig. Zudem funktioniert es auch gut in Verbindung mit Maven und Gradle. Auch hierzu gibt es weitere Informationen auf der Homepage von Manifold.

Arbeiten mit IntelliJ

Manifold funktioniert am besten mit der Entwicklungsumgebung IntelliJ IDEA. Mit dem Manifold-Plug-in für IntelliJ IDEA kann man recht schnell loslegen, es ist leicht über Einstellungen | Plugins | Repositorys durchsuchen | Manifold zu beziehen.

Das Erstellen eines neuen Projekts mit Manifold-Unterstützung ist ebenfalls sehr einfach, ein Demo-Video dazu gibt es hier. Alternativ kann man Manifold auch zu Modulen eines vorhandenen Projekts hinzufügen:

Wer einmal gerne mit Manifold experimentieren möchte, für den lohnt sich ein Blick auf das Beispielprojekt.

Fazit

Als langjähriger Java-Entwickler habe ich persönlich an mehreren Projekten gearbeitet, die eine starke Code-Generierung beinhalten. Ich habe die manchmal verheerenden Auswirkungen deren Verwendung gesehen: stundenlange Build-Zeiten bei Kunden, geradezu zermürbende Entwicklungszyklen, die zeitraubende Entwicklung und Wartung von Code-Generatoren usw.

Es ist an der Zeit für eine bessere Lösung und ich denke, Manifold ist ein guter Ansatz, um dieses Ziel zu erreichen. Type Manifolds bieten eine einladende und produktive DX (Development Experience). Ohne die Notwendigkeit, Code-Generatoren aufrufen und zusätzliche Schritte im Build-Prozess integrieren zu müssen, funktionieren Metadaten einfach.

Man kann noch einiges mehr mit Manifold anstellen, bislang habe ich mit dem Type Manifold API nur an der Oberfläche gekratzt. In zukünftigen Artikeln möchte ich auf die folgenden Themen eingehen:

Bonus – String Templates

Als Bonus für das Lesen bis hierhin, möchte ich eine der neuesten Funktionen von Manifold kurz anschneiden:

Mit einem String Template kann man das Zeichen $ verwenden, um einen Java-Ausdruck direkt in einen String einzubetten. $ kann so etwa verwendet werden, um eine einfache Variable einzubetten:

int hour = 8;
String time = "It is $hour o'clock";  // prints "It is 8 o'clock"

Man könnte auch einen Ausdruck beliebiger Komplexität in geschweifte Klammern einbetten:

LocalTime localTime = LocalTime.now();
String ltime = "It is ${localTime.getHour()}:${localTime.getMinute()}"; // prints "It is 8:39"

Weitere Informationen hierzu gibt es in der Dokumentation. Generell finden sich alle Informationen zum Manifold-Framework auf der Homepage des Projektes.

Verwandte Themen:

Geschrieben von
Scott McKinney
Scott McKinney
Scott McKinney is the founder and principle engineer at Manifold Systems. Previously, he was a staff engineer at Guidewire where he designed and created Gosu. He currently pounds code by the truckload while listening to way too much retro synthwave.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: