About Developing Custom Components with the SDK

Each state in the skill uses either a system component or a custom component.

System components are built into skills and are always available. They provide actions such as outputting text and setting variables. Custom components provide access to business systems such as databases and CRM systems.

To develop a complete custom component, you create an API in Oracle Mobile Hub and define its endpoints along with a backend, which controls access to the API. After the API and backend are defined, you create a connector in Oracle Mobile Hub. The purpose of the connector is to abstract away the connection details to the system of record, removing the need to hard-code the connection details in the implementation. Finally, you write the API’s implementation and upload it to Oracle Mobile Hub.

Create the Custom Component

Custom components are implemented as APIs that expose a GET method and a POST method. At design time, the Skill Builder UI calls the GET endpoint to retrieve the list of services that the API provides. At runtime, the Skill uses the POST request to access the features that the API provides.
Creating a custom component is a two step process. First, you define the endpoints, then you define the implementation. When you define the endpoints, you give them a name, HTTP method, and other parameters. The implementation is the JavaScript that you write and upload to Oracle Mobile Hub. It handles the GET and POST methods that you define for the API.

Define the API Endpoints

You can host the API on any Node.js container, but it's best to use Oracle Mobile Hub for that. Oracle Mobile Hub allows you to use a connector, which provides a level of abstraction between the custom component API and external resources such as a database or other system of record. Otherwise, you'd have to hard code the connection information into your API implementation code.

To define the endpoints for the custom component:

  1. In the side menu, expand Development and click APIs.
  2. Click New API and select API.
  3. Enter a name and a short description for the API and click Create.
  4. When the API page opens, click Endpoints.
  5. Click New Resource.
  6. In the Resource Path field, enter components then click Methods.
  7. Click Add Method and select GET.
  8. When the page for the components endpoint opens, click Save.
  9. Go back to the Endpoints page and click the plus sign beside the components resource. A nested resource is added.
  10. In the Resource Path field for the new resource, enter {componentName} then click Methods.
  11. Select Required then click Add Method and select POST.
  12. Click Save.
When you’re done, the Endpoints page should look like the following screen capture:

Next, click Security and make sure that the Login Required switch is off.

Create and Set up the Backend

A backend acts as a secure gateway between a bot and its custom components, which are implemented as one or more APIs.

After creating the backend, you associate one or more APIs with it. The associated APIs are accessed through the backend, which provides the base URL for the API and handles authentication.

  1. In the side menu, expand Development and click Backends.
  2. Click New Backend.
  3. Enter a name and a description for the backend and click Create.
  4. After the backend is created, associate an API to the backend.
    1. In the side menu for the backend, click APIs.
    2. Click Select APIs.
      The API Catalog opens.
    3. Locate the API that you want to add and click the plus icon.

Create the Connector

A connector provides a level of abstraction between the custom component API and resources external to Oracle Mobile Hub such as a database or other system of record. Although you can connect to the external resource directly from the custom component, it's better to use a connector because changes made to the connection details of the external resource don't require any revisions to your custom component's code.

Before you begin, gather the following information:

  1. The Swagger document that describes the API that you're connecting to. If you don't have a Swagger document, then you need the base URL of the API.

  2. The security credentials of the API that you are connecting to. Connectors support OAUTH, basic auth, SAML, and JWT.

To create the Connector:

  1. In the side menu, expand Development and click Connectors.
  2. Click New Connector and select REST.
  3. Enter a name and a description for the connector and click Create. The configurator opens to the General page.
  4. Click Descriptor and enter the location of the Swagger document. If you don't have a Swagger document, select I don't have a descriptor and enter the base URL of the API.
  5. Click Security and set the security policies for the connector.
  6. Click Test and test the connection to make sure that the connector is working. For example, if you are connecting to a database, make sure that you can send an SQL statement and get the result.
  7. When you are done testing, click Done.

Write the Implementation Code for the Custom Component

After you've defined the API endpoints and set up the backend, you're ready to develop the implementation of the custom component.

The following sample shows the internal structure of a custom component that provides an order number service for a skill bot that takes online orders for a company that sells t-shirts.

tshirtordertaker
\---tshirtordertaker
    |   package-lock.json
    |   package.json
    |   tshirtordertaker.js
    |
    +---components
    |       OrderNumber.js
    |
    +---lib
    |       config.js
    |       database.js

The sample shows a suggested layout of the file structure. The main field in package.json points to tshirtordertaker.js. Within tshirtordertaker.js, you initialize the component middleware, where you define the location of the file or files that implement the custom component.

The following sample shows what thsirtordertaker.js looks like. The location of the custom component implementation is defined in the register option, which in this case is the ./components directory.

/**
 * The ExpressJS namespace.
 * @external ExpressApplicationObject
 * @see {@link http://expressjs.com/3x/api.html#app}
 */

const OracleBot = require('@oracle/bots-node-sdk');

/**
 * Mobile Cloud custom code service entry point.
 * @param {external:ExpressApplicationObject} service 
 */
module.exports = function(service) {
    OracleBot.init(service, {
        logger: console
    });
    
    OracleBot.Middleware.customComponent(service, {
        cwd: __dirname,
        baseUrl: '/mobile/custom/TShirtOrderTaker/components',
        register: './components',
    });
};

The baseUrl value is in the form of /mobile/custom/<API-Name>/components where <API-Name> is the name that you gave the API when you created it in Oracle Mobile Hub. The register value is the directory that contains the file or files that implement the service.

In this case, the components directory has one file, called OrderNumber.js. This file contains the metadata and invoke functions that the bot system requires of every custom component. The following snippet shows what the beginning of OrderNumber.js looks like:

'use strict'

const { DBConnector } = require('../lib/database');
const { DB_CONNECTOR_NAME, DB_CONNECTOR_VERSION } = require('../lib/config');

/**
 * normalize color for db queries
 * @param {string} color 
 */

module.exports = {

    metadata: () => ({
        name: "OrderNumber",
        properties: { customerID: { type: "string", required: true } },
        supportedActions: [
            "instock",
            "backorder"
        ]
    }),

    invoke: (conversation, done) => {

        // deconstruct the conversation to get connectors instance
        const { oracleMobile } = conversation;
        
        if (!oracleMobile) {
            conversation.error(true);
            conversation.reply('The oracleMobile reference was not found. There might be a problem with this API');
            done();
        } else {
            // instantiate DBConnector helper class with backend connectors object
            const db = new DBConnector(oracleMobile.connectors, DB_CONNECTOR_NAME, DB_CONNECTOR_VERSION);
         .
         .
         .       

In this case, a connection to a database is made through a connector API in Oracle Mobile Hub. In the example structure shown earlier, the code for DBConnector would be implemented in database.js. The code for your own implementation will of course be different, but the structure should be the same.

Upload the implementation

The package that you upload is called an implementation archive, and is contained in a .zip file.

The process of uploading the implementation as described here is a manual process. However, command-line options are available. See the Oracle Mobile Hub documentation for instructions on how to download, set up, and use the Custom Code Test Tools that are on Oracle Technology Network (OTN).

  1. Zip the top-level directory to create an implementation archive.
  2. Upload the package to your API.
    1. Log into Oracle Mobile Hub and open your API.
    2. On the API page, click Implementation.
    3. Upload the API. You can click Upload an implementation archive or drag it onto the page.

Test the Implementation

After you upload your implementation, you should test it.

  1. Click Test at the top of the page.
  2. Make sure that the GET /components endpoint is selected
  3. Select the appropriate Backend.
  4. Set the Authentication Method.
  5. Click Test Endpoint.

If the test succeeds, you should see a response status of 200 and a JSON payload. The payload should have the same structure as the following JSON:

{
    "version": "1.1",
    "components": [
        {
            "name": "OrderNumber",
            "properties": {
                "customerID": {
                    "type": "string",
                    "required": true
                }
            },
            "supportedActions": [
                "instock",
                "backorder"
            ]
        }
    ]
}

Add the Custom Component to Your Bot

After the custom component is deployed and ready for operation, add it to your skill bot.

You add the custom component by first setting up a component service in the skill bot UI. Next, you edit the BotML flow to add the state that calls the custom component and the states that handle the response. After everything is set up, test the bot.

Create a Service for Your Custom Component

To set up a service, you need the connection and authentication information of the service that hosts your custom component.

You need the backend ID that hosts the API for your custom component and either the anonymous key for the backend or the credentials for a mobile user. You can get the backend ID and the anonymous key from the summary page for the backend that you created to host the API.

You also need the URL that the service uses to retrieve the custom component's metadata. You can form the metadata URL by copying the URL for the API from the General page of the API Designer and then appending /components to it.

  1. In the designer UI for your skill bot, open the components editor.
  2. Click + Service.
  3. Enter the connection and authentication information for the service that hosts your custom component. If you're using an Oracle Mobile Hub backend, make sure that you append the Metadata URL with /components.
  4. Click Create.

Update the Dialog Flow

The dialog flow in the following procedure is for a t-shirt order taker skill bot. It uses a custom component called OrderNumber, within a state called Get Order Number. When the bot enters this state, the custom component implementation is called by the system. The sample shown corresponds to a custom component which takes a customerID as the input. In this case, the ID is hard-coded but you can also pass in a variable. The custom component also has access to information that the skill bot collected from the user, such as quantity, size, and color. Before responding with either "instock" or "backorder", the custom component sets a variable called orderNumber.

  1. In the designer UI for your skill bot, open the flow editor.
  2. Add the state that uses your custom component.
    Get Order Number:
        component: "OrderNumber"
        properties:
            customerID: "246810"
        transitions:
            actions:
                instock: "Done"
                backorder: "No Stock"
    
  3. Add the Done state.
    Done:
        component: "System.Output"
            properties:
                text: "Come down and pick up your order of ${quantity.value.number} ${color.value} ${size.value} size shirts. Your order number is ${orderNumber.value}"
            transitions:
                return: "Done"
    
  4. Add the No Stock state.
    No Stock:
        component: "System.Output"
            properties:
                text: "Sorry! We have no shirts of that type left, but they are on backorder. The backorder number is ${orderNumber.value}."
                keepTurn: true
        transitions:
            next: "Good Bye"