[Teil 1] dieser Serie (https://liferay.dev/blogs/-/blogs/creating-headless-apis-part-1) nutzt das neue REST Builder-Tool von Liferay, um kopflose APIs zu generieren. Startete ein Projekt zu tun. Im Abschnitt Wiederverwendbare Komponenten haben wir die Definitionen der Anforderungs- und Antwortobjekte definiert: die Vitaminkomponente und eine Kopie der Creator-Komponente von Liferay.
In Teil 2 der Serie haben wir die OpenAPI Yaml-Datei durch Definieren von Pfaden (Endpunkten) vervollständigt und den Code trotz häufiger Probleme erfolgreich generiert.
In diesem Teil erfahren Sie, wie Sie Implementierungscode an der entsprechenden Stelle im generierten Code hinzufügen.
Es gibt vier Code-generierte Module: "Headless-Vitamin-API", "Headless-Vitamin-Client", "Headless-Vitamin-Impl" und "Headless-Vitamin-Test".
build.gradle
noch bnd.bnd
. Es liegt an Ihnen, Abhängigkeiten hinzuzufügen und das Paket zu exportieren. In den folgenden Abschnitten werden die von mir verwendeten Einstellungen geteilt, aber der für die Implementierung erforderliche Satz muss jedes Mal angepasst werden. * *Schauen wir uns jedes Modul einzeln an.
headless-vitamins-api
Das Konzept eines API-Moduls ähnelt dem Service Builder-API-Modul, das eine Schnittstelle für Ressourcen (Dienste) enthält. Es enthält auch spezifische POJO-Klassen für Komponententypen (Vitamin und Creator). Sie sind nicht nur POJOs, die Komponententypklasse verfügt über zusätzliche Setter, die vom Framework beim Deserialisieren von Objekten aufgerufen werden. Schauen wir uns einen der Creator-Komponententypen an.
@JsonIgnore
public void setAdditionalName(
UnsafeSupplier additionalNameUnsafeSupplier) {
try {
additionalName = additionalNameUnsafeSupplier.get();
}
catch (RuntimeException re) {
throw re;
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
Der oben generierte Code ist sehr einfach, aber keine Sorge.
"VitaminResource" ist die Schnittstelle der Ressource (des Dienstes) und wird aus dem in der OpenAPI Yaml-Datei definierten Pfad entnommen. Nach dem Aufruf von REST Builder stellen Sie möglicherweise fest, dass jedem Pfad von "operationId" in der yaml-Datei neue Attribute hinzugefügt werden und diese Werte genau mit den Methoden der Schnittstelle übereinstimmen.
Der generierte Code allein hat zu wenige Methoden, daher werden wir die Schnittstelle hier teilen.
@Generated("")
public interface VitaminResource {
public Page getVitaminsPage(
String search, Filter filter, Pagination pagination, Sort[] sorts)
throws Exception;
public Vitamin postVitamin(Vitamin vitamin) throws Exception;
public void deleteVitamin(String vitaminId) throws Exception;
public Vitamin getVitamin(String vitaminId) throws Exception;
public Vitamin patchVitamin(String vitaminId, Vitamin vitamin)
throws Exception;
public Vitamin putVitamin(String vitaminId, Vitamin vitamin)
throws Exception;
public void setContextCompany(Company contextCompany);
}
Der Pfad / vitamins
, der die Sequenz der Vitaminobjekte zurückgibt, ist die erste Methode getVitaminsPage ()
. Ihre eigene Yaml-Datei deklariert keine PageVitamin-Komponente, sondern eine wird in die exportierte Yaml-Datei eingefügt.
Andere Methoden der Ressourcenschnittstelle stimmen mit den anderen in der Yaml-Datei definierten Pfaden überein. Als nächstes musste ich der build.gradle-Datei des API-Moduls einige Abhängigkeiten hinzufügen:
dependencies {
compileOnly group: "com.fasterxml.jackson.core", name: "jackson-annotations", version: "2.9.9"
compileOnly group: "com.liferay", name: "com.liferay.petra.function"
compileOnly group: "com.liferay", name: "com.liferay.petra.string"
compileOnly group: "com.liferay", name: "com.liferay.portal.vulcan.api"
compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel"
compileOnly group: "io.swagger.core.v3", name: "swagger-annotations", version: "2.0.5"
compileOnly group: "javax.servlet", name: "javax.servlet-api"
compileOnly group: "javax.validation", name: "validation-api", version: "2.0.1.Final"
compileOnly group: "javax.ws.rs", name: "javax.ws.rs-api"
compileOnly group: "org.osgi", name: "org.osgi.annotation.versioning"
}
Wir haben auch geringfügige Änderungen an der Datei "bnd.bnd" vorgenommen, um die Komponenten und Ressourcenschnittstellen verfügbar zu machen:
Export-Package: com.dnebinger.headless.vitamins.dto.v1_0, \
com.dnebinger.headless.vitamins.resource.v1_0
headless-vitamins-client
Der Code in diesem Modul erstellt einen Java-basierten Client zum Aufrufen von Headless-APIs.
Der Client-Einstiegspunkt befindet sich in der Klasse <Paketpräfix> .client.resource.v1_0 <Component> Resource
. In meinem Fall ist dies die Klasse com.dnebinger.headless.vitamins.client.resource.v1_0.VitaminResource
.
Jeder Pfad hat eine statische Methode, jede Methode verwendet dieselben Argumente und gibt dasselbe Objekt zurück. Hinter den Kulissen verwendet jede Methode eine HttpInvoker-Instanz, um den Webdienst unter localhost: 8080 mit [email protected] aufzurufen und Anmeldeinformationen zu testen. Wenn Sie einen Remotedienst testen oder andere Anmeldeinformationen verwenden möchten, müssen Sie die Klasse "
Es ist Sache des Designers, die Hauptklasse und anderen Code zu schreiben, um den Client-Code aufzurufen, aber eine vollständige Client-Bibliothek zum Testen ist ein guter erster Schritt!
Das Modul "Headless-Vitamins-Client" hat keine externen Abhängigkeiten, aber Sie müssen das Paket der Datei "bnd.bnd" exportieren.
Export-Package: com.dnebinger.headless.vitamins.client.dto.v1_0, \
com.dnebinger.headless.vitamins.client.resource.v1_0
headless-vitamins-test
Lassen Sie uns das Modul "Headless-Vitamins-Impl" überspringen und kurz den "Headless-Vitamins-Test" beschreiben.
Der hier generierte Code enthält alle Integrationstests für das Servicemodul und nutzt das Clientmodul zum Aufrufen von Remote-APIs.
In diesem Modul haben wir zwei Klassen, "Base
In der Klasse "VitaminResourceTest" fügen Sie Tests hinzu, die die Klasse "Base" noch nicht implementiert hat. Es ist ein umfangreicher Test zur Verwendung anderer Module und wird zur Fehlerüberprüfung verwendet, wenn versucht wird, einen doppelten Primärschlüssel hinzuzufügen oder ein nicht vorhandenes Objekt zu löschen. Grundsätzlich ist dies ein Test, der nicht einzeln mit einem einfachen Aufruf einer einfachen Ressourcenmethode behandelt werden kann.
Die Datei "build.gradle" für dieses Modul erforderte viele Ergänzungen:
dependencies {
testIntegrationCompile group: "com.fasterxml.jackson.core", name: "jackson-annotations", version: "2.9.9"
testIntegrationCompile group: "com.fasterxml.jackson.core", name: "jackson-core", version: "2.9.9"
testIntegrationCompile group: "com.fasterxml.jackson.core", name: "jackson-databind", version: "2.9.9.1"
testIntegrationCompile group: "com.liferay", name: "com.liferay.arquillian.extension.junit.bridge", version: "1.0.19"
testIntegrationCompile group: "com.liferay.portal", name: "com.liferay.portal.kernel"
testIntegrationCompile project(":modules:headless-vitamins:headless-vitamins-api")
testIntegrationCompile project(":modules:headless-vitamins:headless-vitamins-client")
testIntegrationCompile group: "com.liferay", name: "com.liferay.portal.odata.api"
testIntegrationCompile group: "com.liferay", name: "com.liferay.portal.vulcan.api"
testIntegrationCompile group: "com.liferay", name: "com.liferay.petra.function"
testIntegrationCompile group: "com.liferay", name: "com.liferay.petra.string"
testIntegrationCompile group: "javax.validation", name: "validation-api", version: "2.0.1.Final"
testIntegrationCompile group: "commons-beanutils", name: "commons-beanutils"
testIntegrationCompile group: "commons-lang", name: "commons-lang"
testIntegrationCompile group: "javax.ws.rs", name: "javax.ws.rs-api"
testIntegrationCompile group: "junit", name: "junit"
testIntegrationCompile group: "com.liferay.portal", name: "com.liferay.portal.test"
testIntegrationCompile group: "com.liferay.portal", name: "com.liferay.portal.test.integration"
}
Einige dieser Abhängigkeiten sind die Standardeinstellungen, die nur für Klassen (Junit- und Liferay-Testmodule) benötigt werden, andere hängen vom Projekt ab (Client- und API-Module und möglicherweise andere Module). Es kann einige Versuche und Irrtümer erfordern, um eine Liste zu erhalten, die Ihren Anforderungen entspricht.
Die Datei bnd.bnd
für dieses Modul exportiert keine Klassen oder Pakete und musste nicht geändert werden.
headless-vitamins-impl
Es wird endlich interessant. Dies ist das Modul, das den Implementierungscode enthält. REST Builder hat viel Startercode generiert. Mal sehen, wie es aussieht.
com.dnebinger.headless.vitamins.internal.graphql
, GraphQL ist da! Die kopflose Implementierung enthält GraphQL-Endpunkte, die Abfragen und Mutationen basierend auf definierten Pfaden verfügbar machen. Beachten Sie, dass GraphQL Abfragen und Mutationsänderungen behandelt, indem es "
com.dnebinger.headless.vitamins.internal.jaxrs.application
, hier wird die JAX-RS-Anwendungsklasse gespeichert. Es enthält nichts besonders Interessantes, registriert jedoch die Anwendung im OSGi-Container von Liferay.
com.dnebinger.headless.vitamins.internal.resource.v1_0
, hier nehmen Sie Ihre Codekorrekturen vor.
Die Klasse "OpenAPIResourceImpl.java" ist beispielsweise der Pfad zum Zurückgeben der OpenAPI-Yaml-Datei zum Laden in Swagger Hub. Holen Sie sich für jede Schnittstelle "
Wenn Sie sich die Methoden in der Basisklasse ansehen, sehen Sie, dass sie stark mit Swagger- und JAX-RS-Anmerkungen verziert sind. Werfen wir einen Blick auf eine der Methoden "getVitaminsPage ()", mit denen eine Reihe von in "/ vitamins" gespeicherten Vitaminkomponenten zurückgegeben werden:
@Override
@GET
@Operation(
description = "Retrieves the list of vitamins and minerals. Results can be paginated, filtered, searched, and sorted."
)
@Parameters(
value = {
@Parameter(in = ParameterIn.QUERY, name = "search"),
@Parameter(in = ParameterIn.QUERY, name = "filter"),
@Parameter(in = ParameterIn.QUERY, name = "page"),
@Parameter(in = ParameterIn.QUERY, name = "pageSize"),
@Parameter(in = ParameterIn.QUERY, name = "sort")
}
)
@Path("/vitamins")
@Produces({"application/json", "application/xml"})
@Tags(value = {@Tag(name = "Vitamin")})
public Page<Vitamin> getVitaminsPage(
@Parameter(hidden = true) @QueryParam("search") String search,
@Context Filter filter, @Context Pagination pagination,
@Context Sort[] sorts)
throws Exception {
return Page.of(Collections.emptyList());
}
Wie ist das?
Dies ist einer der Vorteile, die REST Builder für uns bietet. Alle Anmerkungen werden in der Basisklasse definiert, sodass Sie sich keine Sorgen machen müssen.
Werfen wir einen Blick auf die return-Anweisung, die "Page.of (Collections.emptyList ())" übergibt. Dies ist die Stub-Methode, die von der Basisklasse bereitgestellt wird. Es bietet keine wertvolle Implementierung, stellt jedoch sicher, dass ein Wert zurückgegeben wird, wenn er nicht implementiert wird.
Wenn Sie bereit sind, diese Methode zu implementieren, fügen Sie der Klasse "VitaminResourceImpl" die folgende Methode hinzu (derzeit leer):
@Override
public Page<Vitamin> getVitaminsPage(String search, Filter filter, Pagination pagination, Sort[] sorts) throws Exception {
List<Vitamin> vitamins = new ArrayList<Vitamin>();
long totalVitaminsCount = ...;
// write code here, should add to the list of Vitamin objects
return Page.of(vitamins, Pagination.of(0, pagination.getPageSize()), totalVitaminsCount);
}
Beachten Sie, dass keine Anmerkungen vorhanden sind.
Wie bereits erwähnt, sind alle Anmerkungen in der überschreibenden Methode enthalten, sodass alle Konfigurationen fertig sind! Daher wird im Gegensatz zu dem von Service Builder generierten Code der Kommentar "Diese Datei wurde generiert, aber bitte ändern Sie diese Datei nicht" nirgendwo angezeigt. Wenn Sie REST Builder erneut ausführen, wird "@Generated (" ")" als Anmerkung für alle (neu) generierten Klassen angezeigt.
Die Klasse "Base
Wenn Sie die Anmerkungen ändern müssen (** nicht empfohlen **), können Sie dies mit der ResourceImpl-Klasse `
buildscript {
dependencies {
classpath group: "com.liferay", name: "com.liferay.gradle.plugins.rest.builder", version: "1.0.21"
}
repositories {
maven {
url "https://repository-cdn.liferay.com/nexus/content/groups/public"
}
}
}
apply plugin: "com.liferay.portal.tools.rest.builder"
dependencies {
compileOnly group: "com.fasterxml.jackson.core", name: "jackson-annotations", version: "2.9.9"
compileOnly group: "com.liferay", name: "com.liferay.adaptive.media.api"
compileOnly group: "com.liferay", name: "com.liferay.adaptive.media.image.api"
compileOnly group: "com.liferay", name: "com.liferay.headless.common.spi"
compileOnly group: "com.liferay", name: "com.liferay.headless.delivery.api"
compileOnly group: "com.liferay", name: "com.liferay.osgi.service.tracker.collections"
compileOnly group: "com.liferay", name: "com.liferay.petra.function"
compileOnly group: "com.liferay", name: "com.liferay.petra.string"
compileOnly group: "com.liferay", name: "com.liferay.portal.odata.api"
compileOnly group: "com.liferay", name: "com.liferay.portal.vulcan.api"
compileOnly group: "com.liferay", name: "com.liferay.segments.api"
compileOnly group: "com.liferay.portal", name: "com.liferay.portal.impl"
compileOnly group: "com.liferay.portal", name: "com.liferay.portal.kernel"
compileOnly group: "io.swagger.core.v3", name: "swagger-annotations", version: "2.0.5"
compileOnly group: "javax.portlet", name: "portlet-api"
compileOnly group: "javax.servlet", name: "javax.servlet-api"
compileOnly group: "javax.validation", name: "validation-api", version: "2.0.1.Final"
compileOnly group: "javax.ws.rs", name: "javax.ws.rs-api"
compileOnly group: "org.osgi", name: "org.osgi.service.component", version: "1.3.0"
compileOnly group: "org.osgi", name: "org.osgi.service.component.annotations"
compileOnly group: "org.osgi", name: "org.osgi.core"
compileOnly project(":modules:headless-vitamins:headless-vitamins-api")
}
Sie müssen der Datei bnd.bnd nichts hinzufügen, da alle Pakete intern sind.
Sie haben den Punkt erreicht, an dem Sie mit dem Aufbau Ihrer Implementierung beginnen können! Dieses Mal werde ich es abschneiden.
In Teil 1 haben wir ein Projekt erstellt, wiederverwendbare Komponenten definiert und OpenAPI Yaml angesprochen. Es war. Fügen Sie in Teil 2 alle Pfaddefinitionen für den OpenAPI-Dienst hinzu und verwenden Sie den REST Builder zum Codieren Generiert. In Teil 3 (dieser Artikel) haben wir uns den gesamten generierten Code angesehen und festgestellt, dass wir uns nicht um Codeänderungen oder Anmerkungen zum Implementierungscode kümmern müssen.
Schließlich haben wir im letzten Nächsten Teil dem Projekt das Service Builder-Modul zur Datenspeicherung und alles hinzugefügt Implementieren Sie die Ressourcenmethode von und verwenden Sie den Code "ServiceBuilder".
Wir sehen uns wieder!
https://github.com/dnebing/vitamins
Recommended Posts