Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Design-First With Ballerina

DZone's Guide to

Design-First With Ballerina

Read about how a design-first approach to APIs with Ballerina improves developer experience and is optimal for mission-critical APIs.

Free Resource

Share, secure, distribute, control, and monetize your APIs with the platform built with performance, time-to-value, and growth in mind. Free 90 day trial 3Scale by Red Hat

The Need for Design-First

For almost as long as engineers have been integrating disparate/distributed systems together, there have existed the need for a common language, a common vocabulary that can dictate and specify the boundaries of the interfaces, allowing engineers to focus on the internal processing logic while mitigating interoperability concerns.

Though other technologies existed preceding it, an argument can be made that it was with the emergence of SOA and WSDL that an industry wide effort was first made to standardize the design of the interfaces that systems can expose for integration purposes. WSDL is an interface definition language which provides a means of specifying an interface’s operations, messaging format, and data types in a manner that is implementation independent, this is achieved through the language agnostic quality of XML, the base language upon which WSDL was built.

Interface definition languages are tightly coupled with the technologies they are intended to be used with and built upon. With the fallout of XML seen in the last decade, and the rise of new architectural paradigms such as REST and ROA, a void was created in the IDL space. Swagger (Open API) was one such specification written to address this very need.

Apart from acting as a contract, definitions, be it WSDL or Swagger, provide an alternative way of approaching interface creation through the act of defining the contract prior to implementation. This approach referred to as "Design-first," "Contract-First" or "API-First" (a more vendor specific incarnation) allows organizations better segregation between the high-level business requirements and lower functional/non-functional requirements.

Choosing Design-First for API

In their 2017 research on the consequences of API adoption on organizational performance, Benzell, Lagarda, and Alstyne classify API as either closed or open. Closed API is meant for internal consumption, whereas Open API is meant for external consumption, be it B2B or B2C. The researchers find a value increase of 10% and a profit increase of 5% for API adopters, the increase in valuation was driven mainly by Open API.

When designing Open API, those API that are expected to be consumed by outsiders, be it independent developers or strategic partners, it is important to provide a good developer experience for the consumer, as it plays an important role in forming opinions on the organization and the consequent success or failure of the API. As a means of addressing these concerns organizations may turn to “Design-First” and Swagger. Vasudevan identifies the following three situations as being ideal for this approach:

When Developer Experience Matters - a well-designed API is easy to understand and use. As such they can be integrated into a developer’s applications faster, this leads to better adoption.

When Delivering Mission Critical APIs - In a B2B interaction, as the API acts as a channel of communication between the organization and its partners, the API consumption experience plays a role in forming opinions about the organization and such a thought out API design is a must.

When Ensuring Good Communication - At design and implementation time, having a central human readable contract allows for development teams to be in sync and identify design issue at a much earlier stage in the design lifecycle.

According to Vasudevan though “Design-First” approach is best suited for the situations mentioned above code-first approach is likely to produce a better time-to-market. 

Moreover, “Design-first” provides the organization a means of addressing the developer experience through the separation of implementation from design. Similar to the requirement specification endeavors of the monolithic software systems that required engineers specialized in requirement analysis and elicitation, the responsibility of the design can be handed over to those who specialize in the area before the design skeleton be handed over to those who specialize in implementation. The end result of this separation of concerns being the creation of a polished interface that is more in line with the business requirements and is of higher quality. 

In its short period of existence, Swagger(Open API Initiative) has amassed a healthy community reflected by its repository activity and usage in an estimated 10,000 production deployments. In his comparison of Swagger with other API design tools Stowe finds it to have greater community support, language support, and API building tools. Using its many language support and API building tools/projects users may quickly proof their designs or generate implementation specific skeleton code.

Image title

Design-First for Integration

Organizational information systems have moved away from being monolithic and disparate in favor of architectures that provide better agility such as Service Oriented Architectures and Microservices based Architectures. With the adoption of these architectures and the realization of the value creation of Open API, the number of API an organization needs to design and work with has seen an exponential increase. It is no longer enough to ensure the developer experience and quality of singular API, Organizations must work to increase these attributes in groups of API and API interactions. The parallelism posed by API interaction is a requirement that is beyond the capabilities of Swagger and is better addressed by another definition mechanism.

In one post, Dr. Sanjiva Weerawarana, the founder of WSO2 speaks about how the integration space has become more parallel, he goes on to make an analogy of how the increased dependency on external network services in systems is similar to the way in which developers used to have to depend on external libraries to provide different capabilities to their programs. It was with this intent of addressing the lack of proper definition mechanism for interaction/integration mechanism that the graphical syntax of Ballerina was birthed. Ballerina is a general purpose, concurrent and strongly typed programming language that is optimized for integration. Its graphical syntax is designed to resemble UML sequence diagrams, as such it can effortlessly capture the parallelism of integration scenarios as well as providing the missing element in an end-to-end “Design-First” adoption for organizations.

Ballerina Design-First Example

With Ballerina, organizations can now define the contracts of singular API using Swagger and continue on to define the interaction between these API using the same tool. As Swagger is tightly integrated into the ballerina’s design tool, the composer, Swagger definitions seamlessly get translated into Ballerina code and diagrams.

Note: the preceding example is built around the service chaining sample shipped with the ballerina distribution. The ballerina code segments are compatible with the runtime version 0.89 and may not be compatible with other versions.

This example demonstrates how a simple service chaining scenario consisting three API may be constructed with a “Design-First” approach. ABC Bank API; is to act as the entry point for the user, it will orchestrate the interaction between the two other API, Branch LocatorAPI; responsible for locating a branch based on a zip code provided and Bank Info API; responsible for providing information based on a branch code. Once the orchestration is complete, ABC Bank API responds back to the user.

As the first step of constructing this scenario, the swagger definitions for the three API can be created. This step will allow the developer experience to be defined for each singular API, consultants may utilize the Swagger editor provided with the Ballerina Composer for this task or use any other editor they are familiar with. The resultant Swagger definitions will look similar to what’s found below:

ABC Bank API

swagger: '2.0'
basePath: /ABCBank
info:
  version: 1.0.0
  title: Sample Service
paths:
  /locator:
    post:
      operationId: locator
      responses:
        default:
          description: Default Response
        405:
          description: "Invalid input"
      summary: >-
        This resource initiates the orchestration processes between the backend
        services and returns the bank information requested by the consumer.
      consumes:
        - application/json
      produces:
        - application/json
      parameters:
      - in: "body"
        name: "body"
        description: "The locator payload containing the zip code of the consumer."
        required: true
        schema:
          $ref: "#/definitions/RequestPayload"
definitions:
  RequestPayload:
    type: "object"
    properties:
      ATMLocator:
        $ref: "#/definitions/ZipCode"
  ZipCode:
    type: "object"
    properties:
      ZipCode:
        type: "string"

Branch Locator API

swagger: '2.0'
basePath: /branchlocator
info:
  version: 1.0.0
  title: Sample Service
paths:
  /product:
    post:
      operationId: product
      responses:
        default:
          description: Default Response
        405:
          description: "Invalid input"
      summary: >-
        This resource returns the banks name and code based on the zip code provided.
      consumes:
        - application/json
      produces:
        - application/json
      parameters:
      - in: "body"
        name: "body"
        description: "The branch locator payload containing the zip code of the consumer."
        required: true
        schema:
          $ref: "#/definitions/RequestPayload"
definitions:
  RequestPayload:
    type: "object"
    properties:
      BranchLocator:
        $ref: "#/definitions/ZipCode"
  ZipCode:
    type: "object"
    properties:
      ZipCode:
        type: "string"

Bank Info API

swagger: '2.0'
basePath: /bankinfo
info:
  version: 1.0.0
  title: Sample Service
paths:
  /product:
    post:
      operationId: product
      responses:
        default:
          description: Default Response
        405:
          description: "Invalid input"
      summary: >-
        This resource returns information on a bank branch based on the received bank code.
      consumes:
        - "application/json"
      produces:
        - "application/json"
      parameters:
      - in: "body"
        name: "body"
        description: "The branch locator payload containing the zip code of the consumer."
        required: true
        schema:
          $ref: "#/definitions/RequestPayload"
definitions:
  RequestPayload:
    type: "object"
    properties:
      BranchInfo:
        $ref: "#/definitions/BranchCode"
  BranchCode:
    type: "object"
    properties:
      BranchCode:
        type: "string"

At this time, the Ballerina composer requires the swagger definitions to be in JSON format in order for them to be imported. If the definitions were created using a third party editor they may be at once saved as JSON files. If this is not the case the definitions can be converted using free YAML to JSON converters available online.

The JSON conversions for the YAML definitions found above would be:

ABC Bank

{
  "swagger": "2.0",
  "basePath": "/ABCBank",
  "info": {
    "version": "1.0.0",
    "title": "Sample Service"
  },
  "paths": {
    "/locator": {
      "post": {
        "operationId": "locator",
        "responses": {
          "405": {
            "description": "Invalid input"
          },
          "default": {
            "description": "Default Response"
          }
        },
        "summary": "This resource initiates the orchestration processes between the backend services and returns the bank information requested by the consumer.",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/json"
        ],
        "parameters": [
          {
            "in": "body",
            "name": "body",
            "description": "The locator payload containing the zip code of the consumer.",
            "required": true,
            "schema": {
              "$ref": "#/definitions/RequestPayload"
            }
          }
        ]
      }
    }
  },
  "definitions": {
    "RequestPayload": {
      "type": "object",
      "properties": {
        "ATMLocator": {
          "$ref": "#/definitions/ZipCode"
        }
      }
    },
    "ZipCode": {
      "type": "object",
      "properties": {
        "ZipCode": {
          "type": "string"
        }
      }
    }
  }
}

Branch Locator API

{
  "swagger": "2.0",
  "basePath": "/branchlocator",
  "info": {
    "version": "1.0.0",
    "title": "Sample Service"
  },
  "paths": {
    "/product": {
      "post": {
        "operationId": "product",
        "responses": {
          "405": {
            "description": "Invalid input"
          },
          "default": {
            "description": "Default Response"
          }
        },
        "summary": "This resource returns the banks name and code based on the zip code provided.",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/json"
        ],
        "parameters": [
          {
            "in": "body",
            "name": "body",
            "description": "The branch locator payload containing the zip code of the consumer.",
            "required": true,
            "schema": {
              "$ref": "#/definitions/RequestPayload"
            }
          }
        ]
      }
    }
  },
  "definitions": {
    "RequestPayload": {
      "type": "object",
      "properties": {
        "BranchLocator": {
          "$ref": "#/definitions/ZipCode"
        }
      }
    },
    "ZipCode": {
      "type": "object",
      "properties": {
        "ZipCode": {
          "type": "string"
        }
      }
    }
  }
}

Bank Info API

{
  "swagger": "2.0",
  "basePath": "/bankinfo",
  "info": {
    "version": "1.0.0",
    "title": "Sample Service"
  },
  "paths": {
    "/product": {
      "post": {
        "operationId": "product",
        "responses": {
          "405": {
            "description": "Invalid input"
          },
          "default": {
            "description": "Default Response"
          }
        },
        "summary": "This resource returns information on a bank branch based on the received bank code.",
        "consumes": [
          "application/json"
        ],
        "produces": [
          "application/json"
        ],
        "parameters": [
          {
            "in": "body",
            "name": "body",
            "description": "The branch locator payload containing the zip code of the consumer.",
            "required": true,
            "schema": {
              "$ref": "#/definitions/RequestPayload"
            }
          }
        ]
      }
    }
  },
  "definitions": {
    "RequestPayload": {
      "type": "object",
      "properties": {
        "BranchInfo": {
          "$ref": "#/definitions/BranchCode"
        }
      }
    },
    "BranchCode": {
      "type": "object",
      "properties": {
        "BranchCode": {
          "type": "string"
        }
      }
    }
  }
}

Next, the interaction between the three API should be designed. If the consultants used the Ballerina Composer for the previous step they may switch to design view and continue on to define the orchestration behavior using the Ballerina Graphical syntax. When designing this interaction, consultants should utilize Function Constructs to abstract implementation level logic blocks in order to preserve design fidelity. The resultant diagram will look similar to,

Image title

In the above diagram, the logic needed to prepare the payload for the Branch Info API and Bank Info API have been abstracted into functions prepareLocationServicePayload and prepareInfoServicePayload.

import ballerina.lang.messages;
import ballerina.net.http;
import ballerina.lang.system;
import ballerina.lang.jsons;

@http:config {basePath:"/ABCBank"}
service<http> ATMLocator {

    @http:POST{}
    resource locator (message m) {
        http:ClientConnector bankInfoService = create http:ClientConnector("http://localhost:9090/bankinfo/product");
        http:ClientConnector branchLocatorService = create http:ClientConnector("http://localhost:9090/branchlocator/product");
        message backendServiceReq = {};
        message response = {};

        //first stop in the chain.
        backendServiceReq  = prepareLocatorServicePayload(m);
        response  = http:ClientConnector.post(branchLocatorService, "", backendServiceReq);

        //second stop in the chain.
        backendServiceReq  = prepareInfoServicePayload(response);
        response     = http:ClientConnector.post(bankInfoService, "", backendServiceReq);

        //responding to client.
        reply response;

    }    
}

function prepareLocatorServicePayload (message m) (message m2) {
        message msg = m;
        message msgReturn = {};
        json jsonLocatorReq = messages:getJsonPayload(msg);
        string zipCode = jsons:toString(jsonLocatorReq.ATMLocator.ZipCode);
        system:println("Zip Code " + zipCode);
        json branchLocatorReq = {"BranchLocator": {"ZipCode":""}};
        branchLocatorReq.BranchLocator.ZipCode = zipCode;
        messages:setJsonPayload(msgReturn,branchLocatorReq);

        return msgReturn;

}

function prepareInfoServicePayload (message m) (message m2){
        message msg = m;
        message msgReturn = {};
        json branchLocatorRes = messages:getJsonPayload(msg);
        string branchCode = jsons:toString(branchLocatorRes.ABCBank.BranchCode);
        system:println("Branch Code " + branchCode);
        json bankInfoReq = {"BranchInfo": {"BranchCode":""}};
        bankInfoReq.BranchInfo.BranchCode = branchCode;
        messages:setJsonPayload(msgReturn,bankInfoReq);

        return msgReturn;
}

The implementation specialists may pack the services into a single deployable archive, such as this one.

Find out more about Ballerina. Note that the concepts and views presented in this paper are my own and may not reflect that of WSO2.

List of References

[1] The Impact of APIs on Firm Performance

[2] A Design-First Approach to Building APIs with Swagger

[3] Design First or Code First: What’s the Best Approach to API Development?

[4] Contract First REST API Design – How It’s Different & What Are The Benefits

[5] RAML vs. Swagger vs. API Blueprint

[6] Conceiving Ballerina

Discover how you can achielve enterpriese agility with microservices and API management

Topics:
integration ,wso2 ,api ,api design

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}