2012/10/02
2 Oct, 2012

Implementing RESTful services with WSO2 ESB

  • Amila Suriarachchi
  • Architect, Member, Management Committee - Data Technologies - WSO2

Introduction

The WSO2 Enterprise Service Bus (WSO2 ESB) is a high performance and light weight ESB based on Apache Synapse. It is typically used for message routing, transformation, and mediation. In addition to these features service chaining can be used to integrate the data obtained from the different types of systems as well.

The latest addition of the WSO2 ESB API component can be used to implement RESTful support easily. It supports all the default REST operations with the http protocol and URI templates can be used to define the resource URIs. The existing functionality can be used for message transformation, mediation, and routing to make it an ideal system to implement RESTful APIs with the existing back end services.

WSO2 Data Services Server can be used to expose the data stored in different data sources as web services. WSO2 Data Services Server is frequently used to add, update, retrieve, and delete data from an RDBMS system using corresponding SQL statements. This article elaborates on how we can implement a RESTful API to manage a resource called students and implement it using an existing SOAP service developed using WSO2 Data Services Server.

Applies To

WSO2 ESB 4.0.3
WSO2 Data Services Server 2.6.3

Contents

Student REST API

For this sample scenario we assume there is a set of students with the attributes of registration number, name, email, age, class and average. The registration number can be used to uniquely identify the student. For a student with the registration number 001, the resource id would be https://<esb server ip>:<port>/studentes/001. In this case we can obtain student details by sending a GET request to above URI. To add a new student, we can send a POST request to the new student URI with the student data as the payload. Similarly we can update the student details by sending a PUT request with the student new details. Finally student can be deleted using a delete request.

SOAP based data service

The configuration below is used to create the SOAP based data service. This has four operations to add, update, get and delete students. All these details are stored in a back end MySQL server table.

<data name="StudentService">
   <config id="StudentDataSource">
      <property name="org.wso2.ws.dataservice.driver">com.mysql.jdbc.Driver</property>
      <property name="org.wso2.ws.dataservice.protocol">jdbc:mysql://localhost:3306/STUDENT_DB</property>
      <property name="org.wso2.ws.dataservice.user">root</property>
      <property name="org.wso2.ws.dataservice.password">root</property>
   </config>
   <query id="getStudent" useConfig="StudentDataSource">
      <sql>select * from STUDENT_T where REGISTRATIION_NUMBER_C = :registration_number</sql>
      <result element="Students" rowName="Student">
         <element name="RegistrationNumber" column="REGISTRATIION_NUMBER_C" xsdType="xs:string" />
         <element name="Name" column="NAME_C" xsdType="xs:string" />
         <element name="Email" column="EMAIL_C" xsdType="xs:string" />
         <element name="Age" column="AGE_C" xsdType="xs:integer" />
         <element name="Class" column="CLASS_C" xsdType="xs:string" />
         <element name="Average" column="AVERAGE_C" xsdType="xs:double" />
      </result>
      <param name="registration_number" sqlType="STRING" />
   </query>
   <query id="addStudent" useConfig="StudentDataSource">
      <sql>insert into STUDENT_T values (:registrationNumber, :name, :email, :age, :class, :average);</sql>
      <param name="registrationNumber" sqlType="STRING" />
      <param name="name" sqlType="STRING" />
      <param name="email" sqlType="STRING" />
      <param name="age" sqlType="INTEGER" />
      <param name="class" sqlType="STRING" />
      <param name="average" sqlType="DOUBLE" />
   </query>
   <query id="updateStudent" useConfig="StudentDataSource">
      <sql>update STUDENT_T set NAME_C = :name, EMAIL_C = :email, AGE_C = :age, CLASS_C = :class, AVERAGE_C = :average where REGISTRATIION_NUMBER_C = :registrationNumber</sql>
      <param name="registrationNumber" sqlType="STRING" />
      <param name="name" sqlType="STRING" />
      <param name="email" sqlType="STRING" />
      <param name="age" sqlType="INTEGER" />
      <param name="class" sqlType="STRING" />
      <param name="average" sqlType="DOUBLE" />
   </query>
   <query id="deleteStudent" useConfig="StudentDataSource">
      <sql>delete from STUDENT_T where REGISTRATIION_NUMBER_C = :registrationNumber</sql>
      <param name="registrationNumber" sqlType="STRING" />
   </query>
   <operation name="GetStudent">
      <call-query href="getStudent">
         <with-param name="registration_number" query-param="registration_number" />
      </call-query>
   </operation>
   <operation name="AddStudent">
      <call-query href="addStudent">
         <with-param name="registrationNumber" query-param="registrationNumber" />
         <with-param name="name" query-param="name" />
         <with-param name="email" query-param="email" />
         <with-param name="age" query-param="age" />
         <with-param name="class" query-param="class" />
         <with-param name="average" query-param="average" />
      </call-query>
   </operation>
   <operation name="UpdateStudent">
      <call-query href="updateStudent">
         <with-param name="registrationNumber" query-param="registrationNumber" />
         <with-param name="name" query-param="name" />
         <with-param name="email" query-param="email" />
         <with-param name="age" query-param="age" />
         <with-param name="class" query-param="class" />
         <with-param name="average" query-param="average" />
      </call-query>
   </operation>
   <operation name="DeleteStudent">
      <call-query href="deleteStudent">
         <with-param name="registrationNumber" query-param="registrationNumber" />
      </call-query>
   </operation>
</data>

WSO2 ESB configuration

WSO2 ESB uses a special component called API to define the RESTful interfaces. First, users can define a context for the particular API. Under that context they can define a set of resources. A resource definition consists of an operation (i.e GET, POST, PUT, DELETE), an URI template which can be used to define the request URI format and a set of sequences to process the message. These sequences can be used to process the request message and format the response using the available set of ESB mediators. In this example we use the following API to define RESTful operations for student.

<api xmlns="http://ws.apache.org/ns/synapse" name="StudentAPI" context="/students">
    <resource methods="GET" uri-template="/{registrationNumber}" inSequence="StudentGetInSequence" outSequence="StudentGetOutSequence"/>
    <resource methods="POST" uri-template="/{registrationNumber}" inSequence="StudentAddInSequence"/>
    <resource methods="PUT" uri-template="/{registrationNumber}" inSequence="StudentUpdateInSequence"/>
    <resource methods="DELETE" uri-template="/{registrationNumber}" inSequence="StudentDeleteInSequence"/>
</api>  

Sequences can be used to process the message according to the operation and the message format required. Following is the sequences for GET operation.

<sequence xmlns="http://ws.apache.org/ns/synapse" name="StudentGetInSequence">
    <payloadFactory>
        <format>
            <p:GetStudent xmlns:p="https://ws.wso2.org/dataservice">
                <p:registration_number>$1</p:registration_number>
            </p:GetStudent>
        </format>
        <args>
            <arg xmlns:ns="https://org.apache.synapse/xsd" xmlns:ns3="https://org.apache.synapse/xsd" expression="get-property('uri.var.registrationNumber')"/>
        </args>
    </payloadFactory>
    <send>
        <endpoint key="StudentServiceEndpoint"/>
    </send>
</sequence>
        
<sequence xmlns="http://ws.apache.org/ns/synapse" name="StudentGetOutSequence">
    <enrich>
        <source xmlns:m1="https://ws.wso2.org/dataservice" xmlns:ns3="https://org.apache.synapse/xsd" clone="true" xpath="//m1:Students/m1:Student"/>
        <target type="body"/>
    </enrich>
    <send/>
</sequence>

At the InSequence of the GET operation, we need to create the payload to the back end SOAP service and send the request to the back end service end point. For that we use the payload factory mediator and use the variable declared in the URI template to obtain the registration number. At the OutSequence we use the enrich mediator to drop the unnecessary wrapper element.

<sequence xmlns="http://ws.apache.org/ns/synapse" name="StudentAddInSequence">
    <enrich>
        <source xmlns:ns="https://org.apache.synapse/xsd" xmlns:ns3="https://org.apache.synapse/xsd" xmlns:p="https://ws.wso2.org/dataservice" clone="true" xpath="//p:Student/child::node()"/>
        <target type="property" property="studentDetails"/>
    </enrich>
    <payloadFactory>
        <format>
            <p:AddStudent xmlns:p="https://ws.wso2.org/dataservice">
                <p:registrationNumber>$1</p:registrationNumber>
            </p:AddStudent>
        </format>
        <args>
            <arg xmlns:ns="https://org.apache.synapse/xsd" xmlns:ns3="https://org.apache.synapse/xsd" expression="get-property('uri.var.registrationNumber')"/>
        </args>
    </payloadFactory>
    <enrich>
        <source xmlns:ns="https://org.apache.synapse/xsd" xmlns:ns3="https://org.apache.synapse/xsd" clone="true" xpath="get-property('studentDetails')"/>
        <target xmlns:ns="https://org.apache.synapse/xsd" xmlns:ns3="https://org.apache.synapse/xsd" xmlns:p="https://ws.wso2.org/dataservice" action="child" xpath="//p:AddStudent"/>
    </enrich>
    <property name="FORCE_SC_ACCEPTED" value="true" scope="axis2"/>
    <property name="OUT_ONLY" value="true"/>
    <send>
        <endpoint key="StudentServiceEndpoint"/>
    </send>
</sequence>
        
<sequence xmlns="http://ws.apache.org/ns/synapse" name="StudentUpdateInSequence">
    <enrich>
        <source xmlns:ns="https://org.apache.synapse/xsd" xmlns:soapenv="https://www.w3.org/2003/05/soap-envelope" xmlns:ns3="https://org.apache.synapse/xsd" xmlns:p="https://ws.wso2.org/dataservice" clone="true" xpath="//p:Student/child::node()"/>
        <target type="property" property="studentDetails"/>
    </enrich>
    <payloadFactory>
        <format>
            <p:UpdateStudent xmlns:p="https://ws.wso2.org/dataservice">
                <p:registrationNumber>$1</p:registrationNumber>
            </p:UpdateStudent>
        </format>
        <args>
            <arg xmlns:ns="https://org.apache.synapse/xsd" xmlns:soapenv="https://www.w3.org/2003/05/soap-envelope" xmlns:ns3="https://org.apache.synapse/xsd" expression="get-property('uri.var.registrationNumber')"/>
        </args>
    </payloadFactory>
    <enrich>
        <source xmlns:ns="https://org.apache.synapse/xsd" xmlns:soapenv="https://www.w3.org/2003/05/soap-envelope" xmlns:ns3="https://org.apache.synapse/xsd" clone="true" xpath="get-property('studentDetails')"/>
        <target xmlns:ns="https://org.apache.synapse/xsd" xmlns:soapenv="https://www.w3.org/2003/05/soap-envelope" xmlns:ns3="https://org.apache.synapse/xsd" xmlns:p="https://ws.wso2.org/dataservice" action="child" xpath="//p:UpdateStudent"/>
    </enrich>
    <property name="FORCE_SC_ACCEPTED" value="true" scope="axis2"/>
    <property name="OUT_ONLY" value="true"/>
    <send>
        <endpoint key="StudentServiceEndpoint"/>
    </send>
</sequence>

For the POST and PUT sequences, again we need to build the SOAP message request to be sent to the back end data services. For that we use the payload factory mediator. First it saves the child elements with the student details using the enrich mediator. Then use the payload factory mediator to create the payload with the registration number obtained from the request URI. Finally adds the student details again with the enrich mediator. This is an in-only operation so we set the required special properties to send the 202 accepted message to the client.

<sequence xmlns="http://ws.apache.org/ns/synapse" name="StudentDeleteInSequence">
    <payloadFactory>
        <format>
            <p:DeleteStudent xmlns:p="https://ws.wso2.org/dataservice">
                <p:registrationNumber>$1</p:registrationNumber>
            </p:DeleteStudent>
        </format>
        <args>
            <arg xmlns:ns="https://org.apache.synapse/xsd" xmlns:soapenv="https://www.w3.org/2003/05/soap-envelope" xmlns:ns3="https://org.apache.synapse/xsd" expression="get-property('uri.var.registrationNumber')"/>
        </args>
    </payloadFactory>
    <property name="FORCE_SC_ACCEPTED" value="true" scope="axis2"/>
    <property name="OUT_ONLY" value="true"/>
    <send>
        <endpoint key="StudentServiceEndpoint"/>
    </send>
</sequence>

DELELTE sequence is similar to GET sequence where it creates the back end service payload with the payload factory mediator and uses the saved registration number from the URI.

Now we have implemented the student RESTful service. We can use the CURL command which comes with the linux distribution to test the new API. For more details refer the README.txt file under sample.zip

Conclusion

RESTful APIs are widely used to expose the business functionality because of its simplicity and ability to use existing web infrastructure. WSO2 ESB defines a new component called API to define such RESTful APIs. This API component uses an existing set of ESB mediators for message processing and to communicate with the back end systems.

Author

Amila Suriarachchi, Software Architect, WSO2 Inc.

 

About Author

  • Amila Suriarachchi
  • Architect, Member, Management Committee - Data Technologies
  • WSO2 Inc.