Generating java model with inheritance from swagger specification

Nowadays software requires a lot of integration. Services expose their interface
and separate consumers from implementation details. Contract first approach is well known as a best practice since years, and this approach is not restricted to WSDL based web-services. Also JSON based Restful APIs may benefit from specification-first.

One of the tools that supports this approach is Swagger, which is used commonly in our projects.
After your API contract together with data model is specified, you as developer will be often tempted to reuse this definition in your code, so that you do not have to manually synchronize the formal specification and code implementing it. Swagger comes with help and provides code generator for various programming languages. We are interested mostly in generation of a Java based API client code.

Model with inheritance

Let's assume our model is like this: we have an Order, which in turn may contain one or more Order Lines, each can be an Article, a Fee representing sending cost, and a Discount.

This translates to a specification (stored in a swagger.yml file) fragment that could look like this:

  OrderLine:
    type: object
    required:
      - quantity
      - type
      - totalValue
      - unitValue
    discriminator: type
    properties:
      quantity:
        type: integer
        format: int32
      type:
        type: string
        enum:
          - article
          - fee
          - discount
      unitValue:
        type: number
      totalValue:
        type: number
  Article:
    allOf:
      - $ref: '#/definitions/OrderLine'
      - type: object
        required:
          - articleNumber
          - taxRate
        properties:
          articleNumber:
            type: string
          taxRate:
            type: number
  Fee:
    allOf:
      - $ref: '#/definitions/OrderLine'
      - type: object
        required:
          - feeType
        properties:
          feeType:
            type: string
  Discount:
    allOf:
      - $ref: '#/definitions/OrderLine'
      - type: object
        required:
          - discountType
        properties:
          discountCode:
            type: string
          discountType:
            type: string

Example json fragment conforming to this specification can look as follows:

"orderLines" : [
    {
      "type": "article",
      "articleNumber": "997.18",
      "quantity": 1,
      "unitValue": 4.99,
      "totalValue": 4.99,
      "taxRate": 19.0
    },
    {
      "type": "fee",
      "feeType": "payment_fee",
      "quantity": 1,
      "unitValue": 0.49,
      "totalValue": 0.49
    }
]
Why we forked swagger-codegen

Unfortunately, out-of-the-box version of swagger-codegen currently does not support generation of valid client for our model in Java.
Tooling support was not something that should prevent us from using the model we are really willing to use. Thanks to the fact swagger-codegen is Open Source Software, it was possible to implement inheritance support that matches our needs. We decided to publish an enhanced version which can be find at our github repo. To use it clone the repository and just mvn install our fork.
Upstream project plans to include our changes in version 2.2.2, so hopefully in near future you can just omit -zoo in the artifact name and just declare higher version. Note: our current implementation is supporting only one client implementation (which is retrofit2).

How inheritance works

Crucial parts of our specification are usage of allOf syntax (to declare parent definition) and definition of discriminator. According to specification of swagger format only way to provide link between type of data and discriminator value is by keeping names of declared discriminator values same as child data definitions.

To support polymorphism, Swagger adds the support of the discriminator field. When used, the discriminator will be the name of the property used to decide which schema definition is used to validate the structure of the model. As such, the discriminator field MUST be a required field. The value of the chosen property has to be the friendly name given to the model under the definitions property.

Our implementation is case-insensitive, so that it is possible to recognize that "type": "article" should be mapped to Article definition.

Maven configuration

To be able to generate something we need to call swagger-codegen tool and as my project uses maven, this will happen simply by adding swagger-codegen-maven-plugin to pom.xml. This tool consumes some parameters, so first you have to take a look at documentation in swagger-codegen README file.

I am adding following snippet to my build/plugins section, it is configured to use retrofit2 library for http calls:

<plugin>  
    <groupId>io.swagger</groupId>
    <artifactId>swagger-codegen-maven-plugin-zoo</artifactId>
    <version>2.2.1.1</version>
    <executions>
        <execution>
            <id>api-call</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>
                    PATH_TO_SPEC/swagger.yml
                </inputSpec>
                <language>java</language>
                <library>retrofit2</library>
                <addCompileSourceRoot>false</addCompileSourceRoot>
                <configOptions>
                    <dateLibrary>java8</dateLibrary>
                </configOptions>
                <output>${project.build.directory}/generated-sources/swagger/src/main/java</output>
                <apiPackage>zoo.order.api</apiPackage>
                <modelPackage>zoo.order.api.model</modelPackage>
                <invokerPackage>zoo.order.api.client</invokerPackage>
            </configuration>
        </execution>
    </executions>
</plugin>

This will cause generation of client code with model classes.

Interesting aspect is that PATH_TO_SPEC can point eg. to location in artifactory, allowing us to point to shared and versioned specifications instead of just a file.

To have generated code included in application build process we need to make this files visible to maven compiler plugin. We will need build-helper-maven-plugin, configured to place where our model sources have been generated:

<plugin>  
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>build-helper-maven-plugin</artifactId>
    <version>1.12</version>
    <executions>
        <execution>
            <id>add-source</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>add-source</goal>
            </goals>
            <configuration>
                <sources>
                    <source>${project.build.directory}/generated-sources/swagger/src/main/java</source>
                </sources>
            </configuration>
        </execution>
    </executions>
</plugin>  

Only thing that left is to add dependencies required by generated code:

    <dependency>
        <groupId>com.squareup.retrofit2</groupId>
        <artifactId>retrofit</artifactId>
        <version>2.1.0</version>
    </dependency>
    <dependency>
        <groupId>com.squareup.retrofit2</groupId>
        <artifactId>converter-gson</artifactId>
        <version>2.1.0</version>
    </dependency>
    <dependency>
        <groupId>com.squareup.retrofit2</groupId>
        <artifactId>converter-scalars</artifactId>
        <version>2.1.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.oltu.oauth2</groupId>
        <artifactId>org.apache.oltu.oauth2.client</artifactId>
        <version>1.0.1</version>
    </dependency>
    <dependency>
        <groupId>io.gsonfire</groupId>
        <artifactId>gson-fire</artifactId>
        <version>1.8.0</version>
    </dependency>

After trivial configuration our model has been generated and we can focus on solving business issues instead of keeping our eyes on manually written clients and keeping models in sync with service specification.

comments powered by Disqus