2015-12-03

ApexからAWS IoTのAPI叩いてみた

IoT Cloudがいつまで経っても利用できそうにないのでAWS IoTが面白そうなので Apexから触ってみました。今回はThingは利用せずに、Message BrokerにHTTPで送ってRulesで処理をさせるという構成でやってみました。

参考URLはこちら

publishのREST APIの詳細な仕様はあまり載っていない感じですが、以下のようなAPIの仕様になっています。

はこちらを参照↓

リージョンとエンドポイント - アマゾン ウェブ サービス

Apexの実装

publishするだけのサンプルをgithubに上げましたー。

Signature Version4は記載すると長くなるので省略しますが、それ以外の部分を抜粋するとこんな感じです。

public with sharing class AWSIoTClient extends AWSSigV4Client {
    /**
     * iotdata service name
     */
    private final String IOTDATA_SERVICE = 'iotdata';
    
    /**
     * prefix for endpoint.
     */
    private String prefix;
    
    /**
     * @param accessKeyId AWS Access Key ID
     * @param accessKeySecret AWS Access Key Secret.
     * @param region target region.
     * @param prefix prefix for endpoint.
     */
    public AWSIoTClient(String accessKeyId, String accessKeySecret, String region, String prefix) {
        super(accessKeyId, accessKeySecret, region);
        this.prefix = prefix;
    }
    
    /**
     * Publish for AWS IoT topic.
     * @param topicName topic name for publish.
     * @param payload payload for publish.
     * @return String http response for publish rest api.
     */
    public String publish(
        String topicName,
        Map<String, Object> payLoad
    ) {
        Map<String, String> params = new Map<String, String>{};
        
        List<String> signedHeaders = new List<String>{
            'host', 'x-amz-date'
        };
        
        String jsonBody = Json.serialize(payLoad);
        String resource_path = '/topics/' + topicName;
        String hostname = this.prefix + '.iot.' + this.region + '.amazonaws.com';
        
        return this.call(
            IOTDATA_SERVICE,
            hostname,
            signedHeaders,
            params,
            resource_path,
            jsonBody,
            'POST'
        );
    }
}

Signature Version4で利用するservice名はiotdataになります。 ※ここらへんはJavaのSDKのソースコードから確認しています→aws-sdk-java/regions.xml

endpointは{prefix}.iot.{region}.amazonaws.comに向ける感じで。 data.iot.{region}.amazonaws.comも利用できるっぽいですが、availabilityとreachability的にはアカウント固有のエンドポイントの方が良いそうです。(iot-data — AWS CLI 1.9.8 Command Reference

以下のコマンドでアカウント固有のエンドポイントを取得できます。

$ aws iot describe-endpoint

AWS IoT側の設定

設定方法の詳細は以下のサイトが詳しいです。

AWS IoTのいろいろなルールを見てみる&ちょっと試してみる #reinvent | Developers.IO

今回はこんな感じで作成しました。

awsiot_rule1

ActionはS3のバケットに突っ込むやつにしました。

awsiot_rule2

S3に突っ込む以外にもSQS, SNS, Lambda, DynamoDB, Kinesisに飛ばせます。 SNSだとHTTPで任意のエンドポイントに飛ばせるので、連携先の柔軟性は非常に高いです。

ApexからPublish

以下のコードで飛ばせます。

AWSIotClient client = new AWSIotClient(
  'input your access key id,
  'input your secret access key',
  'input your region',
  'input your endpoint prefix'
);

String response = client.publish(
  'hoge',
  new Map<String, Object>{
    'foo' => 101,
    'bar' => 'あいうえお'
  }
);

そうすると、MessageBrokerに配信されて、Rulesで処理されて、S3に格納されます。中身はペイロード(JSON)です。

awsiot_s3input

最近はInvocableMethodやらReport Actionやらで、カスタマイズとApex開発の親和性が上がってきているような気がしているので、これらのものを作成すると汎用的に良い感じにAWS IoTに飛ばせます。Lightning Process BuilderのInvocableMethodだとこんな感じです。(以下の例だと一個しか飛ばせないので複数件飛ばす場合は要修正)

public with sharing class AWSIoTPublishAction {
    @InvocableMethod(label='publish to AWS IoT.' description='')
    public static void publish(List<Parameter> parameters) {
        String accessKeyId = parameters[0].accessKeyId;
        String accessKeySecret = parameters[0].accessKeySecret;
        String region = parameters[0].region;
        String prefix = parameters[0].prefix;
        publish(accessKeyId, accessKeySecret, region, prefix, parameters[0].topic, parameters[0].payload);
    }
    
    public class Parameter {
        @InvocableVariable(required=true)
        public String accessKeyId;
    
        @InvocableVariable(required=true)
        public String accessKeySecret;
    
        @InvocableVariable(required=true)
        public String region;
        
        @InvocableVariable(required=true)
        public String prefix;
        
        @InvocableVariable(required=true)
        public String topic;

        @InvocableVariable(required=true)
        public String payload;
    }
        
    @future(callout=true)
    public static void publish(
        String accessKeyId,
        String accessKeySecret,
        String region,
        String prefix,
        String topic,
        String payload
    ) {
        AWSIotClient client = new AWSIotClient(
            accessKeyId,
            accessKeySecret,
            region,
            prefix
        );
        
        client.publish(
            topic,
            (Map<String, Object>)JSON.deserializeUntyped(payload)
        );
    }
}

今までも、アウトバウンドメッセージを使って、こういった汎用的なコールアウトは出来ていましたが、受け手側のサーバ構築と処理の実装作業が発生していました。また、DMLに応じて任意のAPIを叩きたい場合は、Apexトリガ+futureによる非同期メソッドの実装が必要でした。InvocableMethodでは汎用的なコンポーネントを作成すれば、管理者が設定レベルで利用できるようになります。Apexトリガと違い、オブジェクトと紐付かないので、オブジェクトごとにApex開発をする必要もありません。

またコール先がAWS IoTであれば、サーバ構築が不要な上に、SQLを使ったRulesやDynamoDBやS3へペイロードを保存することもGUIによる設定で簡単に実現できます。また、SQS・Kinesisを介したスケーラブルなプロセスの実装もできますし、AWS Lambdaを利用することでサーバを構築することなく、複雑なビジネスロジックを構築することも可能になります。

 

この投稿は Salesforce App Cloud Advent Calendar 2015の 3日目の記事です。

このエントリーをはてなブックマークに追加