Writing an Adapter

Before starting to create your own adapter, we recommend checking first to see if any existing adapters are avaialable to suit your needs. In addition, be sure to review the Vantiq Adapter and Connector Design to understand the components and how they work together.

An adapter has 3 components:

Defining Data Types

When creating new data type, the only restriction on the data type is that the name of the data type should follow the convention <system>_<type>. This convention eliminates the possibility of naming conflicts if multiple 3rd party systems use the same name.

An adapter may include any number of data types.

Here is snippet of the Salesforce_Opportunity type:

{
    "name": "Salesforce_Opportunity",
    "naturalKey": ["Id"],
    "indexes": [
        { 
            "keys": ["Id"],
            "options": { "unique": true }
        }
    ],
    "properties": {
        "Id": { "type": "String", "required": true },
        "IsDeleted": { "type": "Boolean" },
        "AccountId": { "type": "String" },
        "AccountId_raw": { "type": "String" },
        "Amount": { "type": "Real" },
        "CampaignId": { "type": "String" },
        "CampaignId_raw": { "type": "String" },

        ...

    },
    "foreignKeys" : [
        {
            "type" : "Salesforce_Account",
            "keys" : [ { "property" : "AccountId_raw", "targetProperty" : "Id" } ]
        }
    ]
}

Identifiers

For mutable data (i.e. data that has values that can change), the adapter must be able to update the data based on some key value. Typically, 3rd party systems enforce some key field that can be used to uniquely identify a specific record. Those keys should be defined as natural keys in the data type definition. This allows the adapter to use an UPSERT to save the data, eliminating any necessary logic to determine if the record already exists or not.

It is also a good idea to include a unique index on the identifier field to ensure efficient lookups.

Joins and Foreign Keys

The Visual Rules relies on foreign keys to determine how to correlated data from multiple data types in a single rule. For example, the Salesforce_Opportunity defines a foreign key to the corresponding Salesforce_Account, so that there is an unambiguous way to correlate an opportunity with the corresponding customer.

Creating Inbound Rules

Each data type defined should include an associated rule that is responsible for transforming that specific type into a form that can be saved. Numeric, boolean, and text fields usually do not require any transforms. However, more complex fields, such as dates, GeoJSON, etc can require some manipulation to match Vantiq’s requirements. For example, dates may be represented as strings that may require specific date parsing.

Each inbound rule should be named Adapter_<dataType>_inbound and listen on the /system/adapter/inbound/<dataType> topic. The goal of the rule is to save the record.

The following is an example of an inbound rule to store Salesforce_Opportunity records:

RULE Adapter_Salesforce_Opportunity_inbound

//--------------------------------------------------------
// Listen for data on "/system/adapter/inbound/<type>" topic
//--------------------------------------------------------
WHEN PUBLISH OCCURS ON "/system/adapter/inbound/Salesforce_Opportunity" AS m

//--------------------------------------------------------
// Perform any data tranformations
//--------------------------------------------------------
var obj = m.newValue

var obj = Adapter_Salesforce_ParseDateFields(obj, [ "CloseDate" ])

// Remove extraneous type that is returned from SFDC queries
obj.remove("type")

//--------------------------------------------------------
// Save the data
//--------------------------------------------------------
UPSERT Salesforce_Opportunity(obj)

The adapter does not actually connect to the 3rd party system, but simply listens on a specific topic. This allows flexibility in choosing how to connect, such as directly or through an integration service or bus.

There were only 2 changes that were needed. First, the adapter calls Adapter_Salesforce_ParseDateFields, a custom procedure that parses the standard as well as type specific date fields. Second, the type field was removed as it is returned in the queries but was not part of the type definition.

Finally, saving the record can be performed simply by performing an UPSERT as the natural key has been defined on this data type.

Creating Control Actions

Control actions are supported through Vantiq procedures that have the naming convention, Control_<action>. The responsibility of the action is simply to create the payload for the action message that can be sent to the 3rd party system to trigger the action. The data associated with an action may or may not be related to any specific data type. For example, an action to update a record in a 3rd party system would match the data type definition for that record. However, an action that simply posts a comment may only include a text field. The action procedure should provide an API that makes it clear what the requirements are for the caller.

Here is an example control action procedure that posts a tweet:

PROCEDURE Control_Twitter_PostTweet(body, shortenURLs)

var payload = {
    body: body,
    shortenURLs: shortenURLs
}

Adapter_SendAction("Twitter", "PostTweet", [payload])

There is a set of helpers provided by Vantiq-connector-common. In this case, the Adapter_SendAction helper procedure publishes onto /system/adapter/outbound/<action> the specific action message of the form:

{
    "action": "<action>",
    "payload": [ ...payload... ]
}

The full action name, <action>, is Twitter_PostTweet, which includes the system name to protect against naming collisions across 3rd party systems.

In general, the payload for the action message should be an array. In this case, since only a single tweet was provided, an array of length one was sent. However, for efficiency, the array form allows for batching of actions of the same type into a single message.

Connector Dependencies

Although the adapters are loosely coupled to connectors through the /system/adapter/... events, there may be implicit dependencies on specific connectors. This could happen due to differences in how connectors may transport or marshal data. As such, adapters should always specific and test which connectors they are compatible with.

Example

For a full example, review the Salesforce Adapter.