Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding Swagger server stubs into existing Spring application

What is the best way to plug the server stubs generated by Swagger Codegen into an existing Spring MVC application?

I'm starting off by attempting to use the petstore stubs sample.

My Spring configuration is in Java and looks like this:

public class SpringConfigurationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] { ApplicationContext.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] { WebMvcContext.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

    // ... onStartup etc.

}

WebMvcConfigurationSupport:

@Configuration
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
@PropertySource({ "classpath:config.properties", "file:${CONFIGDIR}/config.properties" })
@ComponentScan(useDefaultFilters = false, basePackages = { "com.yyy", "com.xxx" }, includeFilters = { @Filter(type = FilterType.ANNOTATION, value = Controller.class) })
public class WebMvcContext extends WebMvcConfigurationSupport {

    // ... beans etc.
}

ApplicationContext:

@Configuration
@EnableAsync
@EnableScheduling
@EnableMBeanExport
@Import({SecurityConfig.class, GeneralDBConfiguration.class})
@ComponentScan(useDefaultFilters = true, basePackages = { "com.yyy", "com.xxx" }, excludeFilters = { @Filter(type = FilterType.ANNOTATION, value = {Controller.class, Configuration.class/*, Aspect.class*/}) })
public class ApplicationContext implements AsyncConfigurer {

    // beans etc.

}

How do I go about including the config classes part of the io.swagger.configuration package into my existing application?

Some more details:

One of the problems I'm having is that if I specify a maven dependency on the petshop stubs (which is installed locally by running mvn install:install-file ... from the spring-mvc-j8-async directory):

    <dependency>
        <groupId>io.swagger</groupId>
        <artifactId>swagger-spring-mvc-server</artifactId>
        <version>1.0.0</version>
    </dependency>

Then my spring application finds two AbstractAnnotationConfigDispatcherServletInitializers (one from my app, and the io.swagger.configuration.WebApplication one from swagger-spring-mvc-server) and fails to load up - giving the following exception:

Failed to register servlet with name 'dispatcher'.Check if there is another servlet registered under the same name.

I guess another way to phrase my question would be, how do you use the server stubs generated by swagger-codegen? It looks like I can't just depend on the maven package out of the box...

like image 434
jlb Avatar asked Apr 20 '16 11:04

jlb


1 Answers

I can see this is quite an old one, but I struggled with this a lot and the info I collected might be useful to others until generator docs get improved.

This description is for generating and use spring-mvc server stub from OpenApi 3.0.0 spec using openapi-generator-maven-plugin.

  1. Don't use swagger-codegen, use openapi generator instead: https://github.com/OpenAPITools/openapi-generator (reasoning of the fork is here: https://github.com/OpenAPITools/openapi-generator/blob/master/docs/qna.md)

  2. Create a separate project/module for server stuff and configure maven plugin.

<build>
    <plugins>
        <plugin>
            <groupId>org.openapitools</groupId>
            <artifactId>openapi-generator-maven-plugin</artifactId>
            <version>3.3.4</version>
            <executions>
                <execution>
                    <goals>
                        <goal>generate</goal>
                    </goals>
                    <configuration>
                        <skipIfSpecIsUnchanged>true</skipIfSpecIsUnchanged>
                        <inputSpec>${engine-openapi-spec.location}</inputSpec>
                        <output>${project.build.directory}/generated-sources/openapi</output>
                        <generatorName>spring</generatorName>
                        <library>spring-mvc</library>
                        <apiPackage>eu.dorsum.swift.engine.service.api</apiPackage>
                        <modelPackage>eu.dorsum.swift.engine.service.model</modelPackage>
                        <generateApis>true</generateApis>
                        <generateApiDocumentation>false</generateApiDocumentation>
                        <generateApiTests>false</generateApiTests>
                        <generateModels>true</generateModels>
                        <generateModelDocumentation>false</generateModelDocumentation>
                        <generateModelTests>false</generateModelTests>
                        <generateSupportingFiles>true</generateSupportingFiles>
                        <configOptions>
                            <sourceFolder>src/main/java</sourceFolder>
                            <java8>true</java8>
                            <dateLibrary>java8</dateLibrary>
                            <useTags>true</useTags>
                            <configPackage>eu.dorsum.swift.engine.appconfig</configPackage>
                            <interfaceOnly>false</interfaceOnly>
                            <delegatePattern>true</delegatePattern>
                        </configOptions>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

There are two pieces of configuration which are kind of tricky to figure out.

2.a: Set configOptions/configPackage to a package where your application's root config resides. This option will add your config package as additional basePackage to component scan in OpenAPIUiConfiguration.java: @ComponentScan(basePackages = {"eu.dorsum.swift.engine.service.api", "eu.dorsum.swift.engine.appconfig"}). This is an inverted approach you might think of initially by having the generated mvc config start your existing stuff, but this is the only way I've found requiring no modification in generated code.

2.b: Set configOptions/delegatePattern to true. This one I like a lot! This will generate an additional delegation interface which your server controller can implement. Generated ApiController will delegate all service calls to this interface, so you can plug in your implementation very elegantly. In my setup I have this chain: MessageApi (generated interface) -> MessageApiController implements MessageApi (generated mvc controller) -> MessageApiDelegate (generated interface) -> MessageService implements MessageApiDelegate (my implementation of service methods).

Having these two pieces of config there is no need to modify generated sources. If api spec changes delegation interface changes and you have to implement those changes in MessageService.

Below is an update to my original post to explain in more detail how the service implementation is bound to the delegate interface.

The sources generated by openapi-gen are in a separate jar module containing only swagger.yaml and pom.xml as checked-in 'sources'. The module using the generated code is a war which has a direct dependency on the generated jar. Service implementation is in the war and implements MessageApiDelegate interface which is in the generated source. Spring context is pulled up from the generated code and the packages you define as apiPackage and configPackage in openapi-gen config will be added as basePackages for @ComponentScan in the generated OpenAPIUiConfiguration.java, so your service implementation must be placed in or under apiPackages.

Here is a simplified structure to put the pieces together.

parent/
  swift-engin-service/ (generated jar module)
    swagger.yaml
    pom.xml
    target/generated-sources/openapi/src/main/java/eu/dorsum/swift/engine/
      appconfig/ (generated spring webmvc config)
        OpenAPIUiConfiguration.java
        WebApplication.java (brings up spring context by extending AbstractAnnotationConfigDispatcherServletInitializer)
      service/
        MessageApiController.java (@Controller, @RequestMapping etc.)
        MessageApiDelegate.java (implement this to get your service implementation plugged in the controller)
  swift-engine/ (war module, your code)
    pom.xml
    src/main/java/eu/dorsum/swift/engine/
      appconfig/
        EngineConfig.java (my spring config)
      service/api/ (must be the same as apiPackages property)
        MessageService.java (service implementation)

Relevant part of swift-engine/pom.xml

<project>
    <packaging>war</packaging>

    <dependencies>
        <dependency>
            <groupId>eu.dorsum.core.java.swift-engine</groupId>
            <artifactId>swift-engine-service</artifactId>
        </dependency>
    </dependencies>
</project>

Relevant part of eu.dorsum.swift.engine.service.api.MessageService

@Service
public class MessageService implements MessageApiDelegate {
    @Override
    public ResponseEntity<List<SwiftMessage>> listMessages(List<String> sorting, Integer pageStart, Integer pageSize, String filter) {
        // implementation
    }

    @Override
    public ResponseEntity<Void> updateMessage(SwiftMessage message) {
        // implementation
    }
}

Relevant part of eu.dorsum.swift.engine.appconfig.OpenAPIUiConfiguration (generated)

@Configuration
@ComponentScan(basePackages = {"eu.dorsum.swift.engine.service.api", "eu.dorsum.swift.engine.appconfig"})
@EnableWebMvc
public class OpenAPIUiConfiguration extends WebMvcConfigurerAdapter {
    ...
}
like image 134
Peter Avatar answered Oct 20 '22 20:10

Peter