Open maybeec opened 3 years ago
As described in #13, the feature is a built-in feat of Quarkus(no tkit involved here). The API schema will be still be generated without use of OpenAPI annotations, purely based on Java classes, method signatures etc. This might be ok for local dev or internal APIs, but for public APIs there is much greater emphasis on completeness(e.g. describing all possible response, even the implied ones like 400, 500) and descriptions. In code-first approach this can not be done without using annotations on the corresponding endpoint methods. Usually we the API-first approach for public facing APIs - designer defines the API schema, with full documentation, examples, etc and we then use OpenAPI codegen(there is tkit codegen plugin, but any generator could be used e.g. https://github.com/OpenAPITools/openapi-generator) to generate API interface + model based on it(thus the code is not ,,polluted" with OpenAPI documentation annotations). As Quarkus has built in support for MP Rest client, there is no Impl code required(only API definitions/interfaces and models)
We could generate this realtively easy including the descriptions from JavaDoc, etc. I never put any priority on this in devonfw but if it gets important, I can complete this with a ~2 days effort (but only after the devonfw release is done).
Looking for a tool which can generate the services from the openapi yaml file. Similar to what cobigen does in devonfw currently.
Hi @sujith-mn
we currently use these two tools in tkit:
Openapi generator https://gitlab.com/1000kit/libs/maven/tkit-mp-openapi-plugin
<plugin>
<groupId>org.tkit.maven</groupId>
<artifactId>tkit-mp-openapi-plugin</artifactId>
<version>0.1.0-SNAPSHOT</version>
<executions>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<verbose>true</verbose>
<classesDir>${project.build.outputDirectory}</classesDir>
<configFile>src/main/my.properties</configFile>
<configFileOrdinal>200</configFileOrdinal>
<properties>
<mp.openapi.scan.exclude.packages>org.tkit.parameters.rs.external.v2</mp.openapi.scan.exclude.packages>
</properties>
<propertiesOrdinal>201</propertiesOrdinal>
<format>YAML</format>
<outputFile>${project.build.directory}/openapi.yaml</outputFile>
</configuration>
</execution>
</executions>
</plugin>
Microprofile rest-client generator https://gitlab.com/1000kit/libs/maven/tkit-mp-restclient-plugin
<plugin>
<groupId>org.tkit.maven</groupId>
<artifactId>tkit-mp-restclient-plugin</artifactId>
<version>0.8.0</version>
<executions>
<execution>
<id>test</id>
<goals>
<goal>codegen</goal>
</goals>
<configuration>
<inputSpec>src/main/resources/clients/openapi.yaml</inputSpec>
<output>${project.build.directory}/generated-sources/mprestclient</output>
<apiPackage>gen.org.tkit.test</apiPackage>
<modelPackage>gen.org.tkit.test.models</modelPackage>
<generateSupportingFiles>false</generateSupportingFiles>
<apiInterfaceDoc>false</apiInterfaceDoc>
<fieldGen>LOMBOK</fieldGen>
<jsonLib>JACKSON</jsonLib>
<annotations>
<annotation>javax.inject.Singleton</annotation>
<annotation>org.tkit.quarkus.log.interceptor.LoggerService</annotation>
<annotation>org.eclipse.microprofile.rest.client.inject.RegisterRestClient(configKey="my-client-key")</annotation>
</annotations>
<modelAnnotations>
<modelAnnotation>lombok.ToString</modelAnnotation>
<modelAnnotation>io.quarkus.runtime.annotations.RegisterForReflection</modelAnnotation>
</modelAnnotations>
<configOptions>
<sourceFolder>test</sourceFolder>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
In the Quarkus community there many discussions on this topic, for example: https://github.com/quarkusio/quarkus/issues/3905
My comment from the discussion:
Finally, I would recommend rewriting the generator from scratch and not using "swagger-codegen" or "openapi-generator". I see the following advantages for your own implementation: Maintenance of the project
- Simple and readable code
- You don't have to compromise your implementation in a generic library that tries to fix all problems
- Better integration
The expectation is to generate the JAX RS interfaces and transfer objects out of yaml file (eg: Employee )
Tried OpenAPI Generator
Generated the client code using sample yml file. It uses okttp and supports async call. Microprofile is not been used.
@sujith-mn have you checked the tkit-mp-restclient-plugin
generator?
https://gitlab.com/1000kit/libs/maven/tkit-mp-restclient-plugin
OpenAPI generator does support Microprofile REST client(as well as many other Java client libraries) - OkHttp is simply a default if you dont use any parameters.
The following will generate a perfectly valid (synchronous) MP Rest client: openapi-cli generate -i https://raw.githubusercontent.com/devonfw/cobigen/master/documentation/files/devonfw_employee.yml -g java --library microprofile --skip-validate-spec
Async support would require small moustache template customisation , depending on which async/reactive library you'd like to use. You could e.g. wrap all response objects in CompletionStage
or Uni
(Requires Resteasy Mutiny lib)
side note: the provided yaml schema is invalid(duplicate operationId) hence the --skip-validate-spec
tkit-mp-openapi-plugin throws the below error
[ERROR] Plugin org.tkit.maven:tkit-mp-openapi-plugin:0.1.0-SNAPSHOT or one of its dependencies could not be resolved: Could not find artifact org.tkit.maven:tkit-mp-openapi-plugin:jar:0.1.0-SNAPSHOT -> [Help 1]
@sujith-mn I believe you what to try the tkit-mp-restclient-plugin
and not tkit-mp-openapi-plugin
which generate openap
i schema from java code.
Please check the maven central repository (or README of the project) for the latest release version. https://search.maven.org/artifact/org.tkit.maven/tkit-mp-restclient-plugin
Current release version of the tkit-mp-restclient-plugin
is 0.10.0
Openapi-genertor cli gnerated the APIs (below) and model classes.
package org.openapitools.client.api;
import java.io.File;
import org.openapitools.client.model.ModelApiResponse;
import org.openapitools.client.model.Pet;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.MediaType;
import org.apache.cxf.jaxrs.ext.multipart.*;
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
/**
* OpenAPI Petstore
*
* <p>This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
*
*/
@RegisterRestClient
@RegisterProvider(ApiExceptionMapper.class)
@Path("/pet")
public interface PetApi {
/**
* Add a new pet to the store
*
*/
@POST
@Consumes({ "application/json", "application/xml" })
@Produces({ "application/xml", "application/json" })
public Pet addPet(Pet pet) throws ApiException, ProcessingException;
/**
* Deletes a pet
*
*/
@DELETE
@Path("/{petId}")
public void deletePet(@PathParam("petId") Long petId, @HeaderParam("api_key") String apiKey) throws ApiException, ProcessingException;
/**
* Finds Pets by status
*
* Multiple status values can be provided with comma separated strings
*
*/
@GET
@Path("/findByStatus")
@Produces({ "application/xml", "application/json" })
public List<Pet> findPetsByStatus(@QueryParam("status") List<String> status) throws ApiException, ProcessingException;
/**
* Finds Pets by tags
*
* Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
*
*/
@GET
@Path("/findByTags")
@Produces({ "application/xml", "application/json" })
public List<Pet> findPetsByTags(@QueryParam("tags") List<String> tags) throws ApiException, ProcessingException;
/**
* Find pet by ID
*
* Returns a single pet
*
*/
@GET
@Path("/{petId}")
@Produces({ "application/xml", "application/json" })
public Pet getPetById(@PathParam("petId") Long petId) throws ApiException, ProcessingException;
/**
* Update an existing pet
*
*/
@PUT
@Consumes({ "application/json", "application/xml" })
@Produces({ "application/xml", "application/json" })
public Pet updatePet(Pet pet) throws ApiException, ProcessingException;
/**
* Updates a pet in the store with form data
*
*/
@POST
@Path("/{petId}")
@Consumes({ "application/x-www-form-urlencoded" })
public void updatePetWithForm(@PathParam("petId") Long petId, @Multipart(value = "name", required = false) String name, @Multipart(value = "status", required = false) String status) throws ApiException, ProcessingException;
/**
* uploads an image
*
*/
@POST
@Path("/{petId}/uploadImage")
@Consumes({ "multipart/form-data" })
@Produces({ "application/json" })
public ModelApiResponse uploadFile(@PathParam("petId") Long petId, @Multipart(value = "additionalMetadata", required = false) String additionalMetadata, @Multipart(value = "file" , required = false) Attachment fileDetail) throws ApiException, ProcessingException;
}
The code looks clean and good. With little tweaking we would able to use it. WDYT?
Open Questions:
Open Questions:
- where to configure the target base URL for this REST client?
base URL can be defined as annotation with the API:
eg:@RegisterRestClient(baseUri = "http://localhost:8080/rest-server/rest")
http://download.eclipse.org/microprofile/microprofile-rest-client-1.3.1/apidocs/org/eclipse/microprofile/rest/client/inject/RegisterRestClient.html
Configuring the REST client base URL/URI in the properties file: src/main/resources/META-INF/microprofile-config.properties
org.openapitools.client.api.PetApi/mp-rest/url=http://localhost:8080/rest-server/rest
org.openapitools.client.api.PetApi/mp-rest/scope=javax.inject.Singleton
- could there be multiple different targets per application? - should be
Yes. it can be configured.
- can we configure the target package of generation?
Yes, package can be configured by adding --api-package in the cli. eg: --api-package com.devonfw.animal
Here is a JAX RS server sample generated using openAPI generator maven plugin.
package com.devonfw.application.quarkus.sample.animalmanagement.logic.impl;
import javax.ws.rs.*;
import javax.ws.rs.core.Response;
import io.swagger.annotations.*;
import java.io.InputStream;
import java.util.Map;
import java.util.List;
import javax.validation.constraints.*;
import javax.validation.Valid;
@Path("/greeting")
@Api(description = "the greeting API")
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2021-05-10T11:50:53.483480600+05:30[Asia/Calcutta]")
public class GreetingApi {
@GET
@ApiOperation(value = "", notes = "", response = Void.class, tags={ "misc" })
@ApiResponses(value = {
@ApiResponse(code = 200, message = "Returns greeting on success", response = Void.class),
@ApiResponse(code = 405, message = "Invalid input", response = Void.class)
})
public Response greet(@QueryParam("name") @ApiParam("Optional name of person to greet") String name) {
return Response.ok().entity("magic!").build();
}
}
A template directory also can be configured with the code and the code generation can be done based on the templates. mustash is used for template building.
Here is a version which uses openapi-generator. https://github.com/sujith-mn/devonfw-microservices?organization=sujith-mn&organization=sujith-mn
Here are features:
Here is the yaml file used: https://github.com/sujith-mn/devonfw-microservices/blob/main/reference-project/src/main/resources/openapi.yml
OpenAPI generator, generates the JAX RS server code and client code from OpenAPI/Swagger yaml file in different ways. Scope of this document to discuss only the maven plugin or the CLI.
Using Maven Plugin
<pluginManagement>
<plugins>
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>5.1.0</version>
</plugin>
</plugins>
</pluginManagement>
The below snippet generate the JAX RS APi interface and the model class in the configured directory from the InputSpec file. Here the generated file directory configured to basedirectory/target/generated-sources/main/java
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<executions>
<execution>
<id>openapi-codegen-java-sources</id>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/openapi.yml</inputSpec>
<generatorName>jaxrs-spec</generatorName>
<generateApiTests>false</generateApiTests>
<modelPackage>com.devonfw.application.quarkus.sample.animalmanagement.logic.api.to</modelPackage>
<apiPackage>com.devonfw.application.quarkus.sample.animalmanagement.logic.impl</apiPackage>
<output>${project.basedir}/target</output>
<configOptions>
<interfaceOnly>true</interfaceOnly>
<sourceFolder>generated-sources/main/java</sourceFolder>
<dateLibrary>java8</dateLibrary>
<returnResponse>true</returnResponse>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
The generated API interface looks like below:
@Path("/animals")
@Api(description = "the animals API")
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaJAXRSSpecServerCodegen", date = "2021-05-21T14:31:16.871689+05:30[Asia/Calcutta]")
public interface AnimalsApi {
@GET
@Path("/error")
@ApiOperation(value = "", notes = "", response = Void.class, tags={ })
@ApiResponses(value = {
@ApiResponse(code = 200, message = "OK", response = Void.class)
})
public Response animalsErrorGet();
........................
}
The below code snippet generates the API and model class files using the templates. Mustashe templates used for preparing the template. If templates are not specified, the generator uses default template and generate.
<plugin>
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<executions>
<execution>
<id>openapi-codegen-java-sources</id>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<inputSpec>${project.basedir}/src/main/resources/openapi.yml</inputSpec>
<generatorName>jaxrs-spec</generatorName>
<generateApiTests>false</generateApiTests>
<modelPackage>com.devonfw.application.quarkus.sample.animalmanagement.logic.api.to</modelPackage>
<apiPackage>com.devonfw.application.quarkus.sample.animalmanagement.logic.impl</apiPackage>
<output>${project.basedir}/target</output>
<templateDirectory>${project.basedir}/src/main/templates</templateDirectory>
<configOptions>
<interfaceOnly>false</interfaceOnly>
<sourceFolder>generated-sources/main/java</sourceFolder>
<dateLibrary>java8</dateLibrary>
<returnResponse>true</returnResponse>
</configOptions>
</configuration>
</execution>
</executions>
</plugin>
The generated API class looks like below:
@GET
@Path("/error")
@ApiOperation(value = "", notes = "", response = Void.class, tags={ })
@ApiResponses(value = {
@ApiResponse(code = 200, message = "OK", response = Void.class)
})
public Response animalsErrorGet() {
context.setUriInfo(uriInfo);
context.setHttpHeaders(httpHeaders);
return delegate.animalsErrorGet();
}
.........................
OpenAPI client generation CLI can be used for generating the client. openapi-cli generate -i https://raw.githubusercontent.com/devonfw/cobigen/master/documentation/files/devonfw_employee.yml -g java --library microprofile --skip-validate-spec The above command generates the client code which uses microprofile library .
As a developer I would like generate my latest OpenAPI specification from my Java REST API in order to not need to always switch context and to my changes first in the OpenAPI specification which I am not always used to and then generate the Java REST Services.
We should evaluate what is given by tkit, but also other approaches not focusing on a lot of extra lines to try to specify OpenAPI attributes redundantly in annotations. At least the latter has been identified in the past more as an anti-pattern in Java projects (we can rethink that).