REST API Vision mit Manifold

© Shutterstock / Nomad_Soul
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 dritten Teil unserer Artikelserie zeigt Scott McKinney, wie man Manifold einsetzen kann, um JSON Schema als REST API Single Source of Truth (SSoT) festzulegen. Er geht dabei auch darauf ein, wie das Framework JSON-Schema- und YAML-Ressourcen auf direktem Wege mit Java verbindet, ohne dabei auf Code-Generatoren, kommentierte POJOs oder andere Zwischenlösungen angewiesen zu sein.
Das Problem mit dem REST-API-Design
Die meisten REST APIs werden lediglich im Sinne der Request-/Response-Formate beschrieben, die sie tragen und die in einer bestimmten Definitionssprache verfasst sind, etwa JSON Schema. Ein vollständiges REST-API-Schema stellt aber folgende Informationen bereit:
- sprachneutrale Typdefinitionen
- formale Typ- und Datenbeschränkungen
- eine umfassende Dokumentation
JSON Schema ist allerdings ein sprachneutrales Format, auf das man nicht direkt von Java aus zugreifen kann. Die Typdefinitionen, die durch das Schema definiert sind, sind für das Typsystem von Java nicht sichtbar. Die bevorzugte Lösung für dieses Problem besteht darin, dass man einen Code-Generator in den Build steckt, mit dem die strukturierten Daten zu Java transformiert werden. Dieser Ansatz ist allerdings auch Jahrzehnte alt und birgt andere gefahren: Man macht hier nichts anderes, als ein Problem gegen ein anderes auszutauschen – der Zwischenschritt zur Codegenerierung behindert das, was ansonsten eine sehr geradlinige Entwicklererfahrung (Development Experience) hätte sein können. Weitere Probleme, die mit den Code-Generatoren zusammenhängen, sind unter anderem:
- Kein Feedback: Die Veränderungen im JSON Schema sind erst dann für Java sichtbar, wenn man das API umbaut
- Klassen, die mit veralteten Daten generiert werden (Stale generated Classes)
- Erhöhte Build-Zeiten
- Probleme mit der Skalierbarkeit: gegenseitige Abhängigkeiten zum Code-Generator, Caching, Integrationen usw.
- Schwerwiegende Probleme mit benutzerspezfischen Class Loadern, Runtime Agents und Annotationsprozessoren
- Unterbrechen von Gedankengängen
- Schwache IDE-Integration:
- Keine sofortige Rückmeldung bei Änderungen
- Keine inkrementelle Kompilierung
- Man kann nicht von der Code-Referenz zum entsprechenden JSON-Schema-Element navigieren
- Es kann nicht ausfindig gemacht werden, wo JSON-Schema-Elemente Code nutzen
- Ein Refactoring oder Umbenennen der JSON-Schema-Elemente ist nicht möglich
Die REST-API-Designlösung
In einer Programmiersprachen-Fantasywelt würde der Java-Compiler das JSON Schema direkt verstehen und der Schritt der Code-Generierung wäre nicht nötig – ähnlich der Metaprogrammierung, die Code auf magische Art mit strukturierten Daten in dynamischen Sprachen verbindet. Manifold leistet genau das, ohne dabei die Typsicherheit zu gefährden, denn das universelle Framework erweitert Javas Typsystem auf typsichere Art und Weise. Aufbauend auf dem Framework bietet Manifold auch die Unterstützung für mehrere spezifische Datenformate wie JSON, JSON Schema, YAML und andere. Durch Manifold verfügt Java über die komplette Bandbreite von JSON Schema:
- Der Java-Compiler versteht JSON-Schema-Typdefinitionen
- JSON Schema ist die Single Source of Truth (SSoT) des APIs
- Der Schritt zur Code-Generierung im Build wird eliminiert
- Skalierbar: JSON-Dateien sind Quelldateien
- Keine benutzerdefinierten Class Loader, keine Runtime Agents, keine Annotationsprozessoren
- Erstklassige IDE-Integration:
- JSON-Schema-Änderungen können vorgenommen und sofort im eigenem Code verwendet werden, ohne Kompilieren
- Inkrementell, verarbeitet nur die vorgenommenen Änderungen des JSON-Schemas, schnellere Builds
- Navigation von einer Code-Referenz zu einem JSON-Schema-Element
- JSON-Schema-Elemente lassen sich je nach Nutzung durchsuchen, um Code-Referenzen zu finden
- Refactoring und Umbenennung von Schema-Elementen
- Hotswap-Debugging-Unterstützung
Manifold lässt sich ganz leicht im Projekt einsetzen. Dafür muss man lediglich das Manifold Jar dem Projekt hinzufügen, schon kann man die Vorteile nutzen.
Settings ➜ Plugins ➜ Browse repositories ➜ Search
aufrufen und nach „Manifold“ suchen.Sehen heißt glauben
Die JSON-Schema-Datei com/example/api/User.json
muss nun in das Verzeichnis resources
abgelegt werden:
{ "$id": "https://example.com/restapi/User.json", "$schema": "http://json-schema.org/draft-07/schema#", "title": "User", "description": "A simple user type to uniquely identify a secure account", "type": "object", "definitions": { "Gender": {"enum": ["male", "female"]} }, "properties": { "id": { "description": "Uniquely identifies a user", "type": "string", "format": "email", "readOnly": true }, "password": { "description": "The user's password", "type": "string", "format": "password", "writeOnly": true }, "name": { "type": "string", "description": "A public name for the user", "maxLength": 128 }, "dob": { "type": "string", "description": "The user's date of birth", "format": "date" }, "gender": { "$ref": "#/definitions/Gender" } }, "required": ["id", "password", "name"] }
Nun kann man sofort loslegen und den Typ com.example.api.User
direkt im eigenen Code verwenden – ohne Kompilierungsschritt:
User user = User.builder("scott", "mypassword", "Scott") .withGender(male) .build();
Dieser Code verwendet die builder()
-Methode, die für alle JSON-Schema-Typen verfügbar ist. Die Parameter von builder()
spiegeln die im Schema spezifizierten erforderlichen Typen wider. Die withGender()
-Methode steht für die gender
-Property, die nicht erforderlich ist.
REST-Aufrufe können direkt von User
aus getätigt werden:
Requester<User> req = User.request("http://localhost:4567/users"); // Get all Users via HTTP GET IJsonList<User> users = req.getMany(); // Add a User with HTTP POST User user = User.builder("scott", "mypassword", "Scott") .withGender(male) .build(); req.postOne(user); // Get a User with HTTP GET String id = user.getId(); user = req.getOne("/$id"); // string interpolation too :) // Update a User with HTTP PUT user.setDob(LocalDate.of(1980, 7, 7)); req.putOne("/$id", user); // Delete a User with HTTP DELETE req.delete("/$id");
Alle JSON-Schema-Typen verfügen über eine request()
-Methode, die eine URL entgegennimmt, welche einen Basisstandort darstellt, von dem aus REST-Aufrufe gemacht werden können. Wie gezeigt, bietet request()
Methoden an, um HTTP GET
, POST
, PUT
, PATCH
und DELETE
bequem aufrufen zu können. Via request()
können auch Authentifizierung, Header-Werte usw. bestimmt werden.
Man kann Änderungen an User.json
vornehmen und diese sofort in seinen Code übernehmen. Refactoring und Umbennenungen sind ebenso möglich, wie das Ändern von Typen usw. – diese Änderungen kann man unmittelbar im Code nachverfolgen. Auch die Navigation von Code-Nutzungen zu den entsprechenden Deklarationen in inUser.json
funtkioniert kinderleicht. Die eben erwähnten Code-Nutzungen lassen sich von jedem deklarierten Element in der Datei User.json
ausfindig machen.
Fluent API
JSON-Typen sind als eine Reihe von „flüssigen“ Interface-APIs definiert. Der JSON-Typ User
ist beispielsweise eine Java-Schnittstelle und bietet typsichere Methoden, um
- einen
User
anzulegen, - einen
User
zu erstellen, - die Eigenschaften von einem
User
zu modifizieren, - einen
User
aus einem String, einer Datei oder einer URL überHTTP GET
zu laden, - Web-Service-Prozesse über
HTTP GET
,POST
,PUT
,PATCH
undDELETE
anzufordern, - einen
User
im Format JSON, YAML oder XML zu (be-)schreiben, - einen
User
< zu kopieren,/li> - etwas an den
User
zu senden. Dies geht von jedem strukturell kompatiblen Typ aus, sogar vonMap
und ohne Proxys.
Zusätzlich wird das JSON-Schema-Format vollständig von dem API unterstützt, einschließlich:
- Propertys, die mit
readOnly
oderwriteOnly
markiert sind - Nullable Propertys
additionalProperties
undpatternProperties
- Eingebettete Typen
- Rekursive Typen
- Externe Typen
format
-Typen- Kompositionstypen:
allOf
- Union-Typen:
oneOf
/anyOf
Jedoch werden die Beschränkungen des JSON-Schemas nicht im API durchgesetzt, denn:
- Dies ist der Zweck eines JSON-Schema-Validators
- Die Validierungen von Einschränkungen aufwendig sein kann – es sollte optional sein
Weitere Informationen über die Manifold-Unterstützung für JSON und JSON Schema gibt es in der Dokumentation.
Templates
Mit Manifold ist es möglich, Templates mit der vollen Ausdruckskraft von Java zu erstellen. Die fortschrittliche und leichtgewichtige Template Engine von Manifold, ManTL, unterstützt Java vollständig. Zu den weiteren Features gehört der Support von typsicheren Parametern für Templates, die typsichere Einbindung anderer Templates, gemeinsame Layouts und benutzerdefinierte Basisklassen für anwendungsspezifische Logik.
<%@ import java.util.List %> <%@ import com.example.User %> <%@ params(List<User> users) %> <html lang="en"> <body> <% users.stream() .filter(user -> user.getDob() != null) .forEach(user -> { %> User: ${user.getName()} <br> DOB: ${user.getDob()} <br> <% }); %> </body> </html>
Danach kann das Template typsicher im eigenen Code genutzt werden:
UsersDob.render(users)
Mit dem Manifold-Plug-in für IntelliJ IDEA geht die Template-Authentifizierung noch leichter von der Hand. Dabei stehen Nutzern Features wie deterministische Code-Vervollständigung, In-Line-Hilfe, Nutzungssuche, Highlighting usw. zur Verfügung.
API Server
Man kann Manifold auch in Verbindung mit SparkJava nutzen, um REST-Services zu erstellen. SparkJava ist ein Micro Framework für das Erstellen von Webanwendungen in Java mit minimalem Aufwand. Hier ist eine HTTP PUT
Request Route, um einen User
zu aktualisieren:
// Update existing User put("/users/:id", (req, res) -> UserDao.updateUser(req.params(":id"), User.load().fromJson(req.body())).write().toJson());
Zusätzliche Features
Es gibt viele weitere Funktionen, mit denen Nutzer ihren REST-Service entwickeln können. Leser werden vielleicht bemerkt haben, dass einige der obigen Code-Beispiele das String Templates Feature von Manifold nutzen, das auch als String Interpolation bekannt ist. Das $
-Zeichen kann verwendet werden, um Variablen und Java-Ausdrücke direkt in ein beliebiges String-Literal einzubetten:
int hour = 8; String time = "It is $hour o'clock"; // prints "It is 8 o'clock"
Man kann auch seine eigenen Erweiterungsmethoden zu jedem Java-Typ hinzufügen, einschließlich von JRE-Klassen, wie String
und List
und sogar REST API Interfaces:
@Extension public class MyUserMethods { public static Integer getAge(@This User thiz) { LocalDate dob = user.getDob(); return dob == null ? null : Period.between(user.getDob(), LocalDate.now()).getYears(); } // more extension methods ... }
Dann kann die Methode auch direkt verwendet werden:
User user = ...; int age = user.getAge();
Dies sind nur einige Beispiele der Features des Manifold Frameworks, mit denen Sie Ihr Projekt verfeinern und verbessern können.
Zusammenfassung
In diesem Beitrag habe ich Manifold als ein bahnbrechendes Java-Framework vorgestellt, mit dem der Entwicklungsprozess für REST-APIs optimieren werden kann. Mit Manifold ist es möglich, Code-Generatoren aus dem Build-Skript, einschließlich der Vielzahl der damit verbundenen Probleme, zu eliminieren und mit dem Manifold IntelliJ-IDEA-Plug-in kann die User Experience weiter verbessert werden. Die direkte Navigation zu JSON-Schema-Elementen und die Suche nach deren Nutzung werden damit zum Kinderspiel. Nützlich sind auch das deterministische Refactoring- und Umbenennungs-Tooling und die Möglichkeit, Änderungen schrittweise zu kompilieren. Ich habe auch einige der JSON API Features behandelt, etwa die request()
-Methode für direkte HTTP-Funktionalität. Des Weiteren, habe ich kurz gezeigt, wie Templates, String-Erweiterungen (Interpolation) und Erweiterungsmethoden in ein Projekt integriert werden können. Schließlich habe ich demonstriert, wie man zusammen mit Manifold und SparkJava – einer leichtgewichtigen und leistungsstarken Kombination – einen soliden REST-Service erstellen kann.
Manifold ist eine bahnbrechende Technologie für das Java-Ökosystem. Es ist zudem ein kostenloses Open-Source-Projekt auf GitHub. Alle Informationen zu Manifold gibt es im Repository.
Dieser Artikel wurde ursprünglich auf der Seite Medium veröffentlicht.
Hinterlasse einen Kommentar