Openapi-generator: [Server] support interface and implementation classes for API controllers

Created on 1 Jul 2018  路  8Comments  路  Source: OpenAPITools/openapi-generator

Description

For auto-generated server code, we want to generate 2 files for each API controller file:
1) an interface, which will be overwritten by code generation
2) an implementation class, which will not be overwritten by code generation

Java Spring has already implemented this:
https://github.com/openapitools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/JavaSpring/api.mustache
https://github.com/openapitools/openapi-generator/blob/master/modules/openapi-generator/src/main/resources/JavaSpring/apiController.mustache

The goal is to avoid application/business logic being overwritten by code generation so that there's less overhead when adding/deleting/updating endpoint definition.

Other server generators should leverage similar design.

openapi-generator version

Latest master

Related issues/PRs

https://github.com/swagger-api/swagger-codegen/issues/5431

Suggest a fix/enhancement

If anyone wants to contribute the enhancement, please reply to let us know.

Ada C++ C-Sharp Ceylon Eiffel Elixir Erlang Go Haskell Java Kotlin Nodejs PHP Perl Python Ruby Scala Spring help wanted

All 8 comments

In my opinion this is called the generation gap pattern


I did not check all the java servers, but for jersey2 this is already the case:
Under src/main/java the stuff you edit (and that you no longer regenerate).
Example: PetApiServiceImpl

The Framework classes (jaxrs) are under src/gen/java.
Example: PetApiService (this is a the parent class of PetApiServiceImpl)

The content of src/gen/java should be regenerated after each changes of the input specification.


For me this works as expected and as described here.

rust-server already does this - it generates a standalone library. A server-side user imports the library and _implements_ the API (a Rust trait), whilst a client-side user imports the library and _uses_ the API. The autogenerated code also includes example client/server integrations that users can copy from to make their integration a little easier.

For jaxrs-resteay source code generation, jaxrs annotations are set at class level (see api.mustache).

As proposed, jaxrs annotations could be set at interface level. Of course we still need the class implementation which would inherit from the interface.
This interface could also be used for client source code as described in Resteasy documentation (RESTEasy Proxy Framework).

I seems that having annotation on interface is a jaxrs-resteasy thing. JaxRS-Jersey2 does not seems to support it. See How to annotate JAX-RS on an interface while using Jersey.

From what I experienced neither standalone JaxRS-Jersey2 nor Spring Web could not get you having annotations only on interfaces.
Spring project has this ticket for years https://jira.spring.io/browse/SPR-11055
Spring Framework/Web 5.0.7 supports @RequestParam on interfaces, but not @RequestBody nor @PathVariable.

For Jersey2, as mentioned before such approach also does not work.

I was able get such separation of generated code and implementations by combining Jersey2 and Spring.
There is an ext in Jersey project for this - org.glassfish.jersey.ext:jersey-spring4
With org.springframework.boot:spring-boot-starter-jersey setting it up is even easier and less boilerplate is needed. Check example bellow.

I'd like to have Spring Web based only generated code, and not introducing Jersey2 dependencies at all. When Spring team fixes SPR-11055, transition to Spring Web based annotations on interfaces should be easy - almost as just change generator language.

Lets say PetApi.java is kept in separate module/jar. It could be manually created or regenerated from OpenApi every build.
//---------------- PetApi.java

@Path("/pet")
public interface PetApi {
    @POST
    @Consumes({ "application/json", "application/xml" })
    void addPet(Pet pet);

    @GET
    @Path("/{petId}")
    @Produces({ "application/xml", "application/json" })
    Pet getPetById(@PathParam("petId") Long petId);
}

PetApiImpl.java could be kept in separate jar/module and is under source version control.
//---------------- PetApiImpl.java

import org.springframework.stereotype.Component;

@Component
public class PetApiImpl implements PetApi {

    @Override
    public void addPet(Pet pet) {
        // do stuff 
    }
    @Override
    Pet getPetById(Long petId){
        // do stuff
        // return found Pet
    }
}

//---------------- JerseyConfiguration.java

import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;

@Component
public class JerseyConfig extends ResourceConfig {
    public JerseyConfig() {
        register(PetApiImpl.class);
    }
}

I would love to implement this approach in PHP server generator. It was the most frustrating thing when I worked with SwaggerCodegen. You always need to be careful to not override your current progress. I've ended up with condition in router:

if (class_exists('\MyNamespace\Api\UserApi')) {
    // use implementation
} else {
    // use generated stub
}

I would extend your list with:

  1. an interface, which will be overwritten by code generation
  2. mock class, which is skipped by router when mockingEnabled: false
  3. an implementation class, which will not be overwritten by code generation

With this approach we can generate complete mock API right away.

And I think we need to do the same thing with models. ModelInterface -> ModelMock -> ModelImplementation

Dumb question: Does generator create implementation class for the first time? Or maybe better not to do anyting in src folder at all, so user need to create implementation class manually?

cpp-qt5-qhttpengine-server is implementing this for c++. The method createApiHandlers
https://github.com/OpenAPITools/openapi-generator/blob/master/samples/server/petstore/cpp-qt5-qhttpengine-server/server/src/handlers/OAIApiRouter.h#L68

has to be overridden and that the handlers inherited that is all.

After discussion with @wing328 I decided that abstract class is better than interface for PHP server stubs.
Example with interface:

interface UserApi
{

    /**
     * Operation description
     * 
     * @param ServerRequestInterface $request  Request
     * @param ResponseInterface      $response Response
     * @param array|null             $args     Path arguments
     *
     * @return ResponseInterface
     */
    public function getUserById($request, $response, $arguments);
}

Example with abstract class:

abstract class AbstractUserApi
{

    /**
     * Operation description
     * 
     * @param ServerRequestInterface $request  Request
     * @param ResponseInterface      $response Response
     * @param array|null             $args     Path arguments
     *
     * @return ResponseInterface
     */
    public function getUserById($request, $response, $arguments)
    {
        /// stubs for parsing all request parameters which user can copypaste to implementation
        $userId = $arguments['userId'];        

        /// need to force user to overwrite thit method
        throw new Exception('How about extend AbstractUserApi by \OpenAPIServer\Api\UserApi class implementing getUserById as a GET method?');

        /// or return 501 Not implemented response
        return $request->withStatus(501)->write('How about extend AbstractUserApi by \OpenAPIServer\Api\UserApi class implementing getUserById as a GET method?');
    }
}

Question to community.
What do you think is better by default in just generated stubs, throw an Exception or return 501 Not implemented response :question:

Was this page helpful?
0 / 5 - 0 ratings