2016/03/14
14 Mar, 2016

[Tutorial] How To Turn A Cool Idea Into A Business API with the WSO2 Platform

  • Sanjeewa Malalgoda
  • Director - Engineering | Architect at WSO2 - WSO2

In this tutorial we will design a REST API from scratch using the contract first approach. First, we will write a service definition using Swagger API definition language. Next we will create a web application from that service and deploy it as a service. Thereafter, we will try to invoke the service and use it.

Once you have the web service up and running you can create an API for that service. You can then expose your RESTful web service as a business API. When you deploy the service as a business API you can add some QoS to your API (such as throttling, usage monitoring, etc.).

After following this tutorial you can learn how to develop a complete business API and expose it to the outside world with minimum coding knowledge. If you’re not familiar with the WSO2 product stack or Java web development you can still follow the steps in this tutorial.

Prerequisites



What is Swagger?

According the Swagger official web page, the goal of Swagger is to define a standard, language-agnostic interface to REST APIs that allow both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection. When properly defined via Swagger, a consumer can understand and interact with the remote service with a minimal amount of implementation logic. Similar to what interfaces have done for lower-level programming, Swagger removes the guesswork in calling the service. With swagger, you can define REST API interfaces. The following is a very simple API.

 swagger: "2.0"
info:
  version: "1.0"
  title: "Hello World API"
paths:
  /hello/{user}:
	get:
  	description: Returns a greeting to the user!
  	parameters:
    	- name: user
      	in: path
      	type: string
      	required: true
      	description: The name of the user to greet.
  	responses:
    	200:
      	description: Returns the greeting.
      	schema:
        	type: string
        400:
      	description: Invalid characters in "user" were provided.

In the example above we describe a simple Hello World API. It has a single URL exposed – /hello/{user}. user is the single parameter to the API, defined as part of the path and we say it’s a string. We also describe the successful response and mention that it’s a string as well. A generic 400 error will be received if the user contains invalid characters. You can also see that throughout the operation itself we have provided additional documentation. Therefore, with these steps, you can write a complete API definition in self descriptive manner.

Refer to this document for more details: https://swagger.io/getting-started-with-swagger-i-what-is-swagger/.



Create your service definition

In this step we will be defining a contract for the API interface that will be referred to as the API definition. We use open API specification, formally known as Swagger, to document the API definition.

You can use tools like SwaggerEditor to help you document the API definition in open API spec. To document the APIM Store and Publisher APIs we have used the Swagger YAML format.

Let's consider a simple use case; you’re free to come up with your ideas too. Here, you need to expose the service to external users to find nearby shops based on their geolocation. For this service, we would need geolocation as the input for users and, in response, it will return all shops around the user as shop object array. In addition, you would need to expose the service to obtain quotations for items you’d need to purchase. For this, you need to pass the item name and, in response, you will receive a quotation object.

In our sample service we will have an API with 2 resources.

The first resource is /shops. This resource supports the HTTP get method and you need to pass your geolocation as query parameters. It will then look for shops around you and return an array of shops.

You can start the service definition as follows:

paths:
  /shops:
    get:
      summary: Shops available

The second resource is /estimates/price. This resource will return your estimated price for the product you pass to the API. You need to pass the product name as a query parameter.

The service definition can be started with the following:

  /estimates/price:
    get:
      summary: Price Estimates

In the attached zip file you will find the project source code. It has an API definition as a YAML file, a pom file to build the project, and meta information required to build the web application. To see complete service definition you can go to src/main/resources/api.yaml file and open it using any text editor software.



Creating JAX-RS project

Next we will generate a service skeleton from the API definition that has been created already. We will be using Apache CXF implementation of Java API for RESTful web services. You can use the following guide to setup a basic JAX-RS service: https://cxf.apache.org/docs/jax-rs-maven-plugins.html

To generate a service skeleton from the API definition we will use Swagger for CXF plugin, but first you need to check it out and build it. Check out the following code and build it: https://github.com/hevayo/swagger2cxf-maven-plugin.git

Once you go to this git location you can download the source code then go to the root of that directory and type mvn clean install. Then it will build the project and the JAR file will be installed to your local Maven repo. When you build your code you can use this plugin.



Server stub generation

First you need to include the API definition inside the Maven project; an ideal place to include it is under src/main/resources/api.yaml.

Add the following plugin to the project. Now we are going to use the previously built Swagger to CXF plugin to generate the service skeleton. To do that we need to add the following plugin definition to the pom file. You can find the complete pom file in the root level of the code sample directory that has been provided.

<plugin>
   <groupId>org.wso2.maven.plugins</groupId>
   <artifactId>swagger2cxf-maven-plugin</artifactId>
   <version>1.0-SNAPSHOT</version>
   <configuration>
       <inputSpec>${project.basedir}/src/main/resources/api.yaml</inputSpec>
   </configuration>
</plugin>
<plugin>
   <groupId>org.codehaus.mojo</groupId>
   <artifactId>build-helper-maven-plugin</artifactId>
   <version>1.9.1</version>
   <executions>
       <execution>
           <id>add-source</id>
           <phase>generate-sources</phase>
           <goals>
               <goal>add-source</goal>
           </goals>
           <configuration>
               <sources>
                   <source>src/gen/java</source>
               </sources>
           </configuration>
       </execution>
   </executions>
</plugin>

Since we have provided the project with the pom file and the api.yaml file you can build it from there. Here is the content of the project we have provided.

As you can see we have the pom file and the api.yaml files in project space. Moreover, since we need to build a web application with this we need to add some other classes to the webapp directory as well.

Once you have followed the above steps (these plugins have been already added to the sample we’ve provided) you may generate the code with the following command.

Now let’s use the mvn swagger2cxf:generate command and generate the server stub.

The generated code will be at src/gen/java so we need to add this to the build which can be done via the Maven build helper plugin. You will see the gen directory and impl directory created with some classes.

After the above step, we have all the classes that are required to implement our business logic. The following is an auto generated class for shops API. Now we can implement logic to get shops based on geolocation and return them as shops array.

For this sample implementation we will not be using any complex logic. We will only create a simple object and return it as follows:

public class ShopsApiServiceImpl extends ShopsApiService {
    @Override
    public Response shopsGet(Double latitude,Double longitude){
        // do some magic!
        return Response.ok().entity(new ApiResponseMessage(ApiResponseMessage.OK, "magic!")).build();
    }
}

Replace shopsGet method with following:

public Response shopsGet(Double latitude,Double longitude){
   // do some magic!
   ShopDTO shopDTO = new ShopDTO();
   shopDTO.setFirstName("owner");
   shopDTO.setEmail("[email protected]");
   shopDTO.setShopName("Cake Shop");
   shopDTO.setLastName("Owner");
   return Response.ok().entity(shopDTO).build();
}

In the same way, the estimate API will be created as follows and you can implement your logic there.

public class EstimatesApiServiceImpl extends EstimatesApiService {
    @Override
    public Response estimatesPriceGet(String productName){
        // do some magic!
        return Response.ok().entity(new ApiResponseMessage(ApiResponseMessage.OK, "magic!")).build();
    }
}

Replace estimatesPriceGet method with following:

public Response estimatesPriceGet(String productName){
   // do some magic!
   PriceEstimateDTO priceEstimateDTO = new PriceEstimateDTO();
   priceEstimateDTO.setDisplayName(productName);
   priceEstimateDTO.setProductId("123");
   priceEstimateDTO.setCurrencyCode("USD");
   priceEstimateDTO.setHighEstimate(BigDecimal.valueOf(12));
   priceEstimateDTO.setLowEstimate(BigDecimal.valueOf(8));
   priceEstimateDTO.setEstimate(String.valueOf(10));
   return Response.ok().entity(priceEstimateDTO).build();
}


Configuring API context

In Tomcat and application server the war name is used as context of the web app. Furthermore, you can use # to add sub context to a web app. Refer to Tomcat Naming Convention. We will use this feature to define the context of our API.

Ex. Your sample API context will be /con/test/v1 then, corresponding war for the above context should be con#test#v1.war. As you can see this will allow you to have multiple versions of the API Ex.

To generate a war with the above naming convention use maven-war-plugin with the following configuration:

<plugin>
  <artifactId>maven-war-plugin</artifactId>
  <version>2.2</version>
  <configuration>
     <webResources>
        <resource>
           <directory>src/main/webapp</directory>
        </resource>
     </webResources>
     <warName>con#test#v1</warName>
  </configuration>
</plugin>

Now we have completed all coding required for this web application and we can build this web app and export as .war file. To do so, go to the provided project root and run the following command.

> mvn clean install

Then it will build a whole project and create a web application war file.

Now we can deploy our service in any web application server (for this you can use WSO2 API Manager or WSO2 Application Server as both come with app hosting capabilities). You can use the management console or directly copy the application to the server web apps directory. You may find more information in this article.

Otherwise, you can copy the web application to a web apps directory of the running server using the following command:

> cp target/con#test#v1.war /home/sanjeewa/work/packs/APIM/wso2am-1.9.0/repository/deployment/server/webapps/

If the server is successfully deployed you will see the below logs in the console.

[2016-02-12 19:03:24,871]  INFO - ServerImpl Setting the server's publish address to be /
[2016-02-12 19:03:24,950]  INFO - TomcatGenericWebappsDeployer Deployed webapp: StandardEngine[Catalina].StandardHost[localhost].StandardContext[/con/test/v1].File[/home/sanjeewa/work/packs/APIM/wso2am-1.9.0/repository/deployment/server/webapps/con#test#v1.war]

To see the service definition of a deployed server you may go to the following URL by clicking it on the browser.

https://127.0.0.1:9763/con/test/v1/?_wadl

WSDL content

<?xml version="1.0" encoding="UTF-8"?>
<application>
   <grammars />
   <resources base="https://10.100.1.65:9763/con/test/v1/">
      <resource path="/shops">
         <method name="GET">
            <request>
               <param name="latitude" style="query" type="xs:double" />
               <param name="longitude" style="query" type="xs:double" />
            </request>
            <response>
               <representation mediaType="application/json" />
            </response>
         </method>
      </resource>
      <resource path="/estimates">
         <resource path="/price">
            <method name="GET">
               <request>
                  <param name="product_name" style="query" type="xs:string" />
               </request>
               <response>
                  <representation mediaType="application/json" />
               </response>
            </method>
         </resource>
      </resource>
   </resources>
</application>

To access the deployed service you may type the following curl command and invoke the service.

>curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET https://127.0.0.1:9763/con/test/v1/shops?latitude=6.931944&longitude=79.847778
HTTP/1.1 200 OK
Date: Fri, 12 Feb 2016 14:37:01 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Server: WSO2 Carbon Server

{"email":"[email protected]",
 "shopName":"Cake Shop","firstName":"owner",
 "coordinateLong":null,"picture":null,
 "promoCode":null,"coordinateLat":null,
 "lastName":"Owner"}
>curl -i -H "Accept: application/json" -X GET https://127.0.0.1:9763/con/test/v1/estimates/price?product_name=cake
HTTP/1.1 200 OK
Date: Fri, 12 Feb 2016 14:35:53 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Server: WSO2 Carbon Server

{"productId":"123","estimate":"10","lowEstimate":8,
 "highEstimate":12,"surgeMultiplier":null,
 "currencyCode":"USD","displayName":"cake"}



Expose your service as a business API

Now given that your service has been deployed successfully let's create an API with the service that has been created. Go to API Publisher and create the API with Swagger created already. For this we can use the ‘create API from Swagger’ option.

Figure 1

Provide service URL when you create the API.

https://127.0.0.1:9763/con/test/v1

Figure 2

Follow final steps and publish the API.

Figure 3

Now go to the API Store and navigate to the API that has been created and subscribe to it. Then you will be able to invoke it with the Swagger UI as shown below. The same way you may expose this as a business API and earn some revenue based on API usages. Moreover, you may apply different QoS (such as authentication, throttling, usage, and metering) to your service.

Figure 4



Document generation

Once you have your API deployed in the API Store, API documentation too needs to be included. Let’s see how you can generate API documentation by using the Swagger file that has been created already. To generate a document you can follow the steps below:

Use the following command to checkout the swagger2markup-cli source from github.

To build the source, navigate to root directory of the cloned repository and issue the following command. Make sure the gradlew file is executable.

./gradlew build

Now you can generate the markup documentation for your Swagger definition at swagger_file_path to output_path using following command.

java -jar build/libs/swagger2markup-cli-0.9.2.jar generate \
	-o output_path \
	-g TAGS \
	-i swagger_file_path\
	-l asciidoc

Generated markup files (.adoc) will be created in output_path.

Then you need to convert these markup files to html. To do this you need to install asciidoctor on you system. On linux use sudo apt-get install asciidoctor to install asciidoctor.

Then create a file called index.adoc in output_path where other generated adoc files exist. This file is used to aggregate generated .adoc files into a single file with the left side navigation panel.

Add following content to this file.

:toc: left
:toclevels: 4
include::overview.adoc[]
include::paths.adoc[]
include::definitions.adoc[]

Then issue following command to generate the html files.

asciidoctor index.adoc -n

Documentation for your API can be accessed by opening the index.html file. If you follow all of the steps you will see your API document as follows. Do remember to add all possible information and description to the Swagger definition to generate a comprehensive API document.

Figure 5



Conclusion

Designing your API with an official specification offers significant benefits and should be a priority on any REST API documentation plan. This is especially true when working with multiple teams as you may first start with API definitions and check interoperability. If you have followed that approach you don't have to wait until a working solution is available. Moreover, the Swagger to CXF tool we have used is extremely helpful and effective. With this you can generate almost all the code you need to implement a service; the only thing you have to do here is implement a data access layer.

Another advantage of this Swagger-based implementation is that you may use the same API definition when you create an API in the API Manager. Then you don't need to create the API by passing resource definitions and other attributes in publisher, thus making the API developer's life easy. Moreover, you may use the same definition for API document generation as well. With this you don't need human interaction to generate content and API docs for your API. In summary, this complete process can be used to develop services/APIs/documents with minimum effort by developers.

 

About Author

  • Sanjeewa Malalgoda
  • Director - Engineering | Architect at WSO2
  • WSO2