# コネクターの構築 - オブジェクトのスキーマの定義

このセクションではコネクターの構成方法に関する、Workato の現在の考えについて説明します。この方法論を用いて適切な計画を立てれば、オブジェクト定義とメソッドのフィールドを利用して DRY 原則 (コードの重複を避けるという原則) を守りながら、コネクターに新しいオブジェクトのサポートを簡単に追加できるようになります。以下に示すガイドラインは、Workato の統合開発者が長期にわたって蓄積した知見をまとめたものですが、接続先のアプリケーションや API によっては若干の調整が必要な場合もあります。

ここでは、高度なトピックを含めて Workato SDK の機能に関するこれまでのドキュメントをすでに読み終えており、ターゲットアプリケーションに対するコネクションの定義と確立も済んでいることが前提となっています。まだそれを終えていない場合は、済ませておくことを強くお勧めします。

以降の例は、JSON データを受け入れる API への接続を想定していますが、他のデータ型を受け入れる API にも応用することが可能です。

具体的な説明に入る前に、コネクターコードの構成の概要を示します。

コネクターコードのマインドマップ

主な目標となるのは、コネクターのユーザーが最初に以下のようなアクションやトリガーから選択を行えるようにすることです。

  1. 新規または更新されたオブジェクト
  2. 新規オブジェクト
  3. オブジェクトの作成
  4. オブジェクトの更新
  5. オブジェクトの検索
  6. オブジェクトの削除

ユーザーはそれに続けて、アクションまたはトリガーの設定でオブジェクトを選択します。ユーザーがオブジェクトを選択した後、コネクターは、選択されたオブジェクトに関連する入力項目と出力項目を取得し、表示する必要があります。この処理は、ユーザーが設定項目 (config_fields) に入力した情報に基づいて、適切なオブジェクト定義 (object_definition) を呼び出すことで行われます。ユーザーが設定項目 (config_field) に入力した情報は、入力項目 (input_fields) の lambda 関数に渡される引数に含められます。

実行 (execute) ブロックでは、「動詞 - オブジェクト」の各ペアそれぞれに専用のメソッドを1つずつ使用することをお勧めします。こうすることで、その「動詞 - オブジェクト」アクションに固有な入力データの前処理や、応答データの後処理に対して、コード内に専用のセクションを用意できます。after_error_response メソッドや after_response メソッドといった一般的なデータフォーマット処理は、execute ブロックに直接含めることができます。

このような構成は、究極的には各動詞アクションの処理内容とそのアクションがサポートするオブジェクトを切り離すのに役立ち、新しいオブジェクトのサポートを速やかに追加したり、既存のオブジェクトに対する動詞アクションを追加したりすることを可能にしています。

# オブジェクトのスキーマ定義

アクションやトリガーのコードを書く前には、まず API ドキュメントを確認することが良い習慣です。特定のオブジェクトを扱うリクエストとレスポンスで想定されるペイロードは、非常によく似た構造をしていることが多くあります。

そのような類似があると、同一のスキーマ定義をさまざまなアクションにまたがって使用できる可能性が生まれ、コードの再利用に大きく役立ちます。たとえば XYZ 会計の場合、請求書の同一のスキーマ定義を、「作成」、「読み込み」、「更新」、「削除」、「検索」アクションにまたがって使用できるかもしれません。ここからは、スキーマ定義にどのようなパターンがあるかについて、そしてその定義をコネクターコード内のどこに配置すれば参照しやすいかについて説明します。

Workato において、入力項目や出力項目のスキーマは、動的に決定されることも、静的に決定されることもあります。オブジェクト定義の詳細については、こちらを参照してください。動的か静的かは、構築中のコネクターが対象とする API にメタデータエンドポイントが存在するかどうかに大きく依存しています。以下では、まずスキーマを手作業で定義する方法を例を使って説明し、その後、動的に定義される入力スキーマの例を示します。

簡単に言えば、Workato におけるスキーマ定義とは、オブジェクトのさまざまなプロパティの性質を指示する手段です。input_fields 内で使用されると、それは入力項目として表現されます。output_fields 内で使用されると、それは後続のアクションでの出力データピルとして表現されます。

これらのコードブロックについては、折りたたみ可能なセクションの使用を検討してください。非常に長いコードが表示されていると、ドキュメント内を自由に移動することが難しくなります。

# 例1: 静的に定義されたスキーマ

XYZ 会計へのコネクターを構築している開発者にとって、Workato 内における「Invoice (請求書)」オブジェクトの表現は、たとえば次のようなものになります。

  {
    "TxnDate": "2019-09-19",
    "ID": "1",
    "TotalAmt": 362.07,
    "Line": [
      {
        "Description": "Rock Fountain",
        "SalesItemLineDetail": {
          "Qty": 1,
          "UnitPrice": 275
        },
        "Line-Num": 1,
        "Amount": 275.0,
        "Id": "1"
      },
      {
        "Description": "Fountain Pump",
        "SalesItemLineDetail": {
          "Qty": 1,
          "UnitPrice": 12.75
        },
        "LineNum": 2,
        "Amount": 12.75,
        "Id": "2"
      }
    ],
    "DueDate": "2019-10-19",
    "DocNumber": "1037",
    "Deposit": 0,
    "Balance": 362.07,
    "CustomerRef": {
      "name": "Sonnenschein Family Store",
      "value": "24"
    },
    "BillEmail": {
      "Address": "Familiystore@intuit.com"
    },
    "BillAddr": {
      "Line1": "Russ Sonnenschein",
      "Long": "-122.1141681",
      "Lat": "37.4238562",
      "Id": "95"
    },
    "MetaData": {
      "CreateTime": "2014-09-19T13:16:17-07:00",
      "LastUpdatedTime": "2014-09-19T13:16:17-07:00"
    }
  }

一方、「Invoice」作成アクションは、たとえば次のような POST リクエストを必要とします。

  POST /invoice/create
  Content type:application/json

  {
    "Line": [
      {
        "Description": "Fountain straws",
        "SalesItemLineDetail": {
          "Qty": 100,
          "UnitPrice": 0.075,
        },
        "Line-Num": 1,
        "Amount": 7.50,
        "Id": "192 "
      },
    ],
    "CustomerRef": {
      "value": "1"
    }
  }

また、「Invoice」更新アクションは、たとえば次のような POST リクエストを必要とします。

  POST /invoice/update
  Content type:application/json

  {
    "ID": "1",
    "Line": [
      {
        "Description": "Fountain straws",
        "SalesItemLineDetail": {
          "Qty": 100,
          "UnitPrice": 0.075,
        },
        "Line-Num": 1,
        "Amount": 7.50,
        "Id": "192 "
      },
    ],
    "CustomerRef": {
      "value": "1"
    }
  }

原則的には、Workato 内のオブジェクトのスキーマを定義する場合、それをできるだけ多くの種類のアクション (請求書作成アクションや請求書更新アクションなど) で再利用できるようにすべきです。そのため、私たちが定義するスキーマは、この「Invoice」オブジェクトが持ち得るパラメータすべての上位集合になっているべきです。そのため、次のようなスキーマに行き着くことになります。

  object_definitions: {
    invoice: lambda do |connection, config_fields|
      [
        { name: "Id" },
        { name: "TxnDate" },
        { name: "TotalAmt", type: "number" },
        {
          name: "Line",
          type: "array",
          of: "object",
          properties: [
            { name: "Description" },
            {
              name: "SalesItemLineDetail",
              type: "object",
              properties: [
                { name: "Qty", type: "number" },
                { name: "UnitPrice", type: "number" }
              ]
            },
            { name: "Line-Num", type: "number" },
            { name: "Amount", type: "number" },
            { name: "Id" }
          ]
        },
        { name: "Due-Date" },
        { name: "Doc Number" },
        { name: "Deposit", type: "number" },
        { name: "Balance", type: "number" },
        {
          name: "CustomerRef",
          type: "object",
          properties: [
            { name: "name" }
            { name: "value" }
          ]
        },
        {
          name: "BillEmail",
          type: "object",
          properties: [
            { name: "Address" }
          ]
        },
        {
          name: "BillAddr",
          type: "object",
          properties: [
            { name: "Line1" },
            { name: "Lon" },
            { name: "Lat" },
            { name: "Id" }
          ]
        },
        {
          name: "MetaData",
          type: "object",
          properties: [
            { name: "CreateTime", type: "date_time" },
            { name: "LastUpdatedTime", type: "date_time" }
          ]
        }
      ]
    end
  }

このスキーマは、invoice という名前が付けられた object_definition (オブジェクト定義) の内部に記述されます。

XYZ 会計で請求書 ID が自動生成されるなどの場合は、請求書の作成時にユーザーに ID を定義させてはいけませんが、そのような場合でも、後で請求書 ID を入力スキーマから簡単に外すことができます。

# 例2: 動的に定義されたスキーマ

多くの場合は、入力/出力項目を手作業で定義するのではなく、可能であればメタデータエンドポイントを利用して、それらの項目を生成することが強く推奨されます。この方法を取れば、基本オブジェクトに新しい項目が追加された場合に必要となる拡張作業を減らすことができます。また、アプリケーションがカスタム項目に対応している場合は、そのサポートを追加することもできます。この例では、HubSpot (opens new window) を使用します。このツールには、「Contact (コンタクト)」オブジェクトのプロパティを表現するメタデータエンドポイントが用意されています (こちら (opens new window)を参照)。

このようなケースでは、そちらのエンドポイントにリクエストを発行し、そのレスポンスを利用して Workato が理解できる形式の入力/出力スキーマを構築するとよいでしょう。以下に示すのは、HubSpot のメタデータエンドポイントから返されるレスポンスのサンプルです。このレスポンスから得られるのは JSON オブジェクトの配列であり、その各要素は、それぞれが「Contact」のプロパティの1つを表現しています。

[
  {
    "name": "example_property_name",
    "label": "Example Property Name",
    "description": "Example Description of the property",
    "groupName": "contactinformation",
    "type": "string",
    "fieldType": "text",
    "options": [
      // if property is a dropdown, all options are detailed here
    ],
    "deleted": false,
    "displayOrder": -1,
    "readOnlyValue": false,
    "readOnlyDefinition": false,
    "hidden": false,
    "mutableDefinitionNotDeletable": false,
    "favorited": false,
    "favoritedOrder": -1,
    "calculated": false,
    "externalOptions": false,
    "displayMode": "current_value",
    "formField": true
  },
  // More properties below
]

これを利用して、先ほどの静的な定義の例と同じ引数 action_type を取る contact_schema というメソッドを定義できます。

  object_definitions: {
    contact: lambda do |connection, config_fields|
      get('/properties/v1/contacts/properties').map do |property|
        field = {
          name: property['name'],
          label: property['label'],
          hint: property['description'],
          type: call('type_mapping', property['type']),
          control_type: call('control_type_mapping', property['fieldType'])
        }

        if %w[select multiselect].include?(field[:control_type])
          picklist = {
            pick_list: property['options'].
            map { |option| [option['label'], option['value']]  }
          }
          field = field.merge(picklist)
        end

        if %w[boolean select multiselect].include?(field[:control_type])
          togglefield = {
            toggle_hint: 'Select manually',
            toggle_field: {
              name: property['name'],
              label: property['label'],
              type: 'string',
              control_type: 'text',
              toggle_hint: 'Map datapill',
              hint: "Enter in a valid option"
            }
          }
          field = field.merge(togglefield)
        end

        field
      end
    end,
  },

methods: {  
    type_mapping: lambda do |input|
      case input
      when 'datetime'
        'date_time'
      when 'number'
        'integer'
      when 'booleancheckbox'
        'boolean'
      when 'bool'
        'boolean'
      when 'enumeration'
        'string'
      else
        input
      end
    end,

    control_type_mapping: lambda do |input|
      case input
      when 'textarea'
        'text-area'
      when 'datetime'
        'date_time'
      when 'booleancheckbox'
        'checkbox'
      when 'bool'
        'checkbox'
      when 'enumeration'
        'select'
      when 'radio'
        'select'
      when 'checkbox'
        'multiselect'
      else
        input
      end
    end
  }

このメソッドでは、HubSpot からレスポンスを取得し、各プロパティの値を Workato のスキーマで定義されたパラメータにマッピングしています。またほかにも、type_mappingcontrol_type_mapping という2つのサービスメソッドを作成しました。これらのメソッドの役割は、HubSpot のデータ型 type から Workato の type、HubSpot のデータ型 fieldType から Workato の control_type へのマッピングを定義することです。

もう1つ注目すべきことは、ピックリストとトグル項目をサポートする control_types を対象に、その両者を導入したことです。基本的に、ピックリストと文字列トグル項目を併用することは強く推奨されます。そうすることで、エンドユーザーは静的に値を選択することも、その代わりにデータピルをマッピングすることもできるようになります。使いやすさの向上についての詳細は、また後で説明します。

独自の API に対するコネクターを Workato 上で構築する場合は、メタデータエンドポイントについて検討することを強くお勧めします。これは、カスタム項目を持つアプリケーションでは特に重要なことです。メタデータエンドポイントを利用すると、ユーザーのインスタンスに固有の項目に対応する入力項目を生成できるようになるためです。

# アクションの作成

選択した基本オブジェクトのスキーマを構築する方法について学び終えたので、次は、いま定義したメソッドを利用して、最初のアクションの構築に取り掛かりましょう。


Last updated: 2023/8/31 1:07:14