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.