Sencha ExtJS/Touch で Browserify を活用する

Browserifyについて

JavaScript には10年以上の歴史がありますが、標準的なモジュールの仕組みが現れたのはごく最近です。CommonJSと呼ばれるこの方式は、サーバサイドJavaScriptの代表格である Node.js に採用された*1ことで徐々に浸透してきましたが、最もJavaScriptを利用する機会の多いWebブラウザ環境においては、JavaScriptをローディングする仕組みの制限もあり*2、長らくこの仕組を使うことはできませんでした。

Browserifyは、こうしたCommonJS形式のモジュール参照を識別して解決し、1つのJSファイルにまとめてくれるプログラムです。配布/テスト前にソースコードにBrowserifyを適用しておくことで、Webブラウザ環境でも必要なモジュールをロード済みの状態で実行できるようになります。

特に、Browserifyは Node.jsの標準モジュールおよびパッケージマネージャ(npm)で配布されるライブラリなどについても組み込むことができるため、Node.jsのエコシステムで育まれた資産をWebブラウザ環境にも流用することができる点が大きなメリットになります。

なお、WebブラウザにおけるJavaScriptなどのコンポーネント管理のための仕組みとしては、他にBowerなどがありますが、利用しているコンポーネントの管理やスクリプトのロードの指定についてはHTML中に記述する必要が出てくるため、JavaScriptのモジュール管理という点だけで考えると管理が煩雑になるきらいがあります。

Senchaとの組み合わせ

話は変わって、SenchaというコンポーネントベースのHTML5フレームワークがあります。Sencha ExtJSはPCブラウザを想定したもので、Sencha Touchはタッチベースのスマートデバイスを前提としています。どちらもあらかじめUIコンポーネントが多数用意されており、JavaScriptコード内にコンポーネントJSONで指定するだけで簡単にUIの構築ができるところが魅力の1つです。

しかしながら、Sencha ExtJS/Touchには独自のモジュールローディングの仕組みがあり、あまりこれら標準的なモジュールシステムとの組み合わせの相性がよくありません。特に、Browserifyでは静的にモジュールの参照解決をビルド時に行いファイル結合するのに対し、Senchaでは(少なくともDevフェーズにおいては)クラスごとの依存関係を調べて該当するモジュールパスにあるJSファイルを動的にロードするようになっているのも、問題を難しくしています。

たとえば、以下のソースコードではJSファイル(app/view/Main.js)内に記述されたJSON設定の中のrequiresプロパティからモジュールを識別し、パスをたどってJSファイルを読み込むようになっています。

app/view/Main.js

Ext.define('MyApp.view.Main', {
    extend: 'Ext.panel.Panel',
    requires:[
        // コード内でapp/view/MyPanel.jsを参照していることを明示
        'MyApp.view.MyPanel'
    ],
    constructor: function() {
        var myPanel = Ext.create('MyApp.view.MyPanel', {});
        // ...
    },
    ....
});

app/view/MyPanel.js

// app/view/Main.jsから参照されているため、このファイルは自動的に読み込まれる
Ext.define('MyApp.view.MyPanel', {
    extend: 'Ext.form.Panel',
    constructor: function() {
        // ...
    },
    ....
});

問題点

ここで別プロジェクトで作成していたCommonJS形式のモジュール、あるいはnpmで配布されているライブラリを上記コンポーネント内で利用したいと思ったとします。 Senchaでは開発フェーズにおいては各クラスモジュールのソースコードファイルを別々にロードしますので、Browserifyで単一のファイルに纏めることはできません。また、先にBrowserifyを適用してしまうと、ソースコードファイルの配置されているパス関係も崩れてしまうので、Senchaのローディングシステムが上手く動かないことが容易に想像されます。

app/view/MyPanel.js

Ext.define('MyApp.view.MyPanel', {
    extend: 'Ext.panel.Panel',
    constructor: function() {
        this.callParent(arguments);
        // npm で配布されている underscore.js モジュールを参照
        var _ = require('underscore');
        var tmpl = _.template('<p>Hello, <b><%= name %></b></p>')
        var html = tmpl({ name: 'John' });
        // ...
    },
    ....
});

ストレートに考えると、以下の方法が思いつくかもしれません。

  1. 依存しているモジュールをそれぞれBrowserify化しておき、Senchaからは外部ファイルとしてロードする
  2. 常にSenchaコマンドを使ってファイルをビルドし、出力ファイルに対してさらにBrowserifyを適用する

1 の場合は、今までどおりSenchaで外部JSファイルを利用する場合とほぼ同じため、多くを望まなければこれで特に支障はないかもしれません。ただし以下の様な問題点があります。

  • 外部Senchaコード内でCommonJS形式のモジュール参照の記述が使えない
  • Senchaから利用するライブラリが増えるたびにそれぞれ別個にビルドし参照に加えなければならない
  • Senchaでライブラリを参照するために、それぞれ常にグローバルに名前空間を作ってライブラリを展開する必要がある(具体的にはBrowserifyのstandaloneオプションを使ってそれぞれビルド出力する必要がある)

2 の場合は、既にSencha内で参照しているモジュールは全て組み込まれた状態であり、そのファイルに対してBrowserifyを適用するので、お互いに干渉することはなさそうですが、以下の様な問題があります。

  • 毎回Senchaコマンド(Sencha専用のビルドツール)を利用して1つのファイルにまとめるのは、開発フェーズではコストが高い作業である
  • Senchaコマンドでの結合ではSourceMapが適用されないため、開発フェーズで全て1ファイルにまとめてしまうとデバッグ時に苦労する

これらの問題によりSenchaとCommonJS(Browserify)の共存が難しくなっています。Node.jsによる資産をSenchaで流用するのが難しくなるだけではなく、Senchaとは本質的に関係のないモジュールまでSenchaのローディングシステムを使って開発してしまい、他プロジェクトでの再利用を妨げてしまうなどの弊害もあります。なんとか解決できないものでしょうか。

extract-required による解決

ここで、上記とは違ったアプローチを考えてみます。基本的には1.のアプローチに近い形になるのですが、要するに、Sencha形式で書かれたソースコードの中から、CommonJS形式で呼ばれているモジュールを自動的に抽出できればよいのではないでしょうか。そうすれば上記の1の問題点は解決となりそうです。

そのために、新しいプログラムを用意しました。ここで紹介するextract-requiredは、JavaScriptソースコード中からCommonJS形式のモジュール参照を検出し、モジュール名をリストで出力するモジュールです。内部でEsprimaを利用しJavaScriptコードの静的解析を行って、require() 関数の呼び出しを検出しています。

ここではこのextract-requiredをGruntタスク化したgrunt-extract-requiredを利用し、ソースコード中に含まれるモジュール参照をリストしたソースコードファイルを生成するようにします。

Gruntfile.coffee

module.exports = (grunt) ->

    require("load-grunt-tasks")(grunt)

    grunt.initConfig
        pkg: require "./package"

        watch:
            sencha:
                files: [ "app/**/*.js" ]
                tasks: [ "build" ]

        extract_required:
            sencha:
                files: [
                    src: [ "app/**/*.js" ]
                    dest: "build/common/required.js"
                ]

        browserify:
            sencha:
                files:
                    "build/common/required-bundle.js" : [
                        "build/common/required.js"
                    ]
            options:
                bundleOptions:
                    standalone: "require"

        clean:
            sencha:
                src: [ "build/common" ]

    grunt.registerTask "build", [ "extract_required", "browserify" ]
    grunt.registerTask "default", [ "build" ]

上記のGrunt設定ファイルでは、ビルド時にextract_requiredタスクを走らせてbuild/common/required.jsファイルに出力します。出力されたrequired.jsファイルは対象となるソースコード内で参照されているモジュールへのrequire()コールと、モジュールを外部から参照するための関数が含まれます。

build/common/required.js

var requireCalled;
module.exports = function (name) { // 外部からモジュールを参照するための関数
  // prevent recursive require call
  if (requireCalled) { throw new Error("Cannot find module '" + name + "'"); }
  requireCalled = true;
  try {
    return require(name);
  } finally {
    requireCalled = false;
  }
};
require("underscore");
require("datejs");
require("q");
...

このファイルに対してbrowserifyタスクを実行することで、参照しているモジュールの中身をすべて含む1つのJSファイル(build/common/required-bundle.js)ができあがります。browserifyタスクにはstandaloneオプションを"require"に設定していますので、モジュール参照のための関数はrequireという名前でグローバルに公開されます。Senchaベースのソースコードからrequire()呼び出しによってモジュールが参照される時は、この公開されたrequire()関数を呼び出すことになります。

あとは下記のようにして、HTMLファイルに対してBrowserifyによって生成されたJavaScriptファイル(build/common/required-bundle.js)を含めるようにしておけば大丈夫です。

index.html

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="UTF-8">
    <title>MyApp</title>
    <!-- <x-compile> -->
        <!-- <x-bootstrap> -->
            <link rel="stylesheet" href="bootstrap.css">
            <script src="ext/ext-dev.js"></script>
            <script src="bootstrap.js"></script>
        <!-- </x-bootstrap> -->
        <script src="build/common/required-bundle.js"></script>
        <script src="app.js"></script>
    <!-- </x-compile> -->
</head>
<body></body>
</html>

サンプルコードなど

サンプルとなるSenchaのプロジェクトはこちらにあります。

まとめ

Senchaは非常に魅力的なUIコンポーネント群を提供してくれていますので、対象となるプロジェクトによっては大変有用だと思われます。しかしながら、近年のJavaScriptのコミュニティによる開発の最先端はほぼNode.jsに端を発するものがほとんどであり、ほぼ標準であると言えますので、これらを無視していくのはあまりにももったいないことです。

本当はSenchaを使うときは骨の髄までSencha流に染まるべきなのかもしれませんが、それでもNode.js界隈の資産を活用できるように工夫するのは、それなりに有意義だと考えています。

*1:正確にはCommonJSのModule1.0相当、さらにそれに独自方式も加えられている模様 http://meso.hatenablog.com/entry/20110626/1309082158

*2:スクリプトファイルの読み込みが非同期でしかできないため、非同期専用の特別な形式(AMD)でモジュール参照を記述する必要がある

リアルタイムBaaS「GoInstant」の概要

はじめに

レッドオーシャン化するBaaS市場

昨年より国内でも盛り上がりを見せつつあるBaaS(Backend as a Service)ですが、グローバルでは昨年だけでも Parse.com のFacebookによる買収StackMob の PayPal による買収とその後のサービス終了のアナウンスなど、サービスベンダー統廃合の波が押し寄せています。GoogleMicrosoftSalesforce.com、そして Amazon などの著名なクラウドベンダーが揃ってこの分野に対して食指を伸ばしてきているのも、すでにBaaSはレッドオーシャンになりつつあるということを示唆しています。

このような中で、残されたBaaSベンダーは、それぞれ自らが生き残る市場を求めて方針をシフトさせてきています。

現在もっとも顕著な動きは、これまでコンシューマ向けのモバイル開発者をターゲットとしていたのを、次第にエンタープライズをターゲットとするベンダーが増えてきたということです。

そして、もう一つ特筆すべき動きとして、これまでの全方位的なバックエンドを受け持つのではなく、ある機能にフォーカスしたサービスを行うBaaSが出てきたということです。この中でも特にリアルタイム・メッセージングを実現するためのBaaSが最近注目を浴びています。

重要性を増す「リアルタイム」

リアルタイム・メッセージングは、古くからチャットやメッセンジャー、オンラインゲームといったサービスで利用されてきましたが、一般的な従来型のWebアプリではクライアントリクエスト/サーバレスポンスによる通信パターンが主流で、あまり普及しているとは言えませんでした。

しかしながら、昨今のスマートデバイスの隆盛と、それに伴なってクライアント端末側に高いエクスペリエンスを要求するアプリケーションが増えてきたこともあり、サーバの状態をリアルタイムに複数のクライアント端末に通知する必要性が次第に高まってきています。

こうしたリアルタイム・メッセージングの実現のためには、WebSocketや非同期処理といった技術を利用する必要がありますが、インフラ構成としてもアプリケーション開発者としても、最初から構築するのはなかなか骨が折れる作業です。しかもその上でスケーラビリティを担保するのはかなりの職人芸が必要になります。

GoInstantとは

本記事で紹介するGoInstantは、このようなリアルタイム・メッセージングのバックエンドインフラをサービス開発者に代わって実現してくれるサービスです。

GoInstantでは、BaaSで一般的に求められるクラウド上でのデータストレージ機能に加え、複数のユーザ間でリアルタイムにデータ同期を行う機能を提供しています。さらにPub/subベースのメッセージ基盤を備えており、デバイスやユーザをまたがった双方向のコミュニケーションに活用できます。

なおGoInstantは、2012年7月にすでにSalesforce.comによって7000万ドルで買収されていますが、同社によって買収されたHerokuと同様に、Salesforceとはサービス的に独立した子会社として運営されている模様です。

使い方

サインアップ

GoInstantには無償利用できる枠(100同時ユーザ、2GBストレージ上限)が用意されているため、サインアップしてすぐ利用を開始することができます。

アプリケーション作成

取得したアカウントでGoInstantにログインするとダッシュボード画面が開きます。まずは Create App ボタンを押して新規のアプリケーションを作成します。アプリケーションを作成すると、Connect URL の欄にURLが表示されます。このURLが次のJavaScriptを作成する際に必要になります。

GoInstant.jpg

HTMLとJavaScriptコードの記述

続いて GoInstant のサービスを呼び出すためのHTMLファイルとJavaScriptコードを作成します。

<!DOCTYPE html>
<html>
  <head>
    <script src="//cdn.goinstant.net/v1/platform.min.js"></script>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
    <script>
var url = "https://goinstant.net/9cdd2d926d23/helloworld";
var conn, room, Messages;

$(init);

// コネクションの初期化とキーオブジェクトの取得
function init() {
  goinstant.connect(url)
    .then(function(result) {
      conn = result.connection;
      room = result.rooms[0];
      Messages = room.key('messages');
    })
    .then(queryMessages)
    .then(watchMessage)
    .catch(function(err) {
      console.error(err);
    });
  handleEvents();
}

// サーバから最新のメッセージ一覧を取得
function queryMessages() {
  var yesterday = Date.now() - 24*60*60*1000;
  return Messages.query({ 
    timestamp: { $gte : yesterday }
  }, {
    sort: { timestamp: 'desc' },
    limit: 10
  })
  .execute()
  .then(function(results) {
    results = results.reverse();
    $.each(results, function(i, result) {
      appendMessageToList(result.value);
    });
  });
}

// テキスト投稿のイベントを処理
function handleEvents() {
  $("#postBtn").on('click', function() {
    Messages.add({
      text: $("#messageText").val(),
      timestamp: Date.now()
    });
  });
}

// メッセージキーに対するデータ追加の通知をハンドルする
function watchMessage() {
  Messages.on('add', { local: true }, function(message) {
    appendMessageToList(message);
  });
}

// リストにメッセージを表示追加
function appendMessageToList(message) {
  var messageListEl = $('#messages');
  while (messageListEl.children().size() >= 10) {
    messageListEl.children(":last-child").remove();
  }
  $('<li>').text(message.text).prependTo(messageListEl);
}

    </script>
  </head>
  <body>
    <input type="text" id="messageText">
    <button id="postBtn">Post</button>
    <ul id="messages"></ul>
  </body>
</html>

以上は、GoInstantが提供するストレージを利用した、テキスト投稿サービスの例です。バックエンドのプログラムを書かなくてもHTML+JavaScriptのみでこのようなサービスが作成できます。

上記のスクリプトでは、ストレージ内のメッセージデータのクエリ及びデータの追加を行っていますが、特に注目していただきたいのは、watchMessage() 内でサーバ側のストレージへのメッセージ追加のイベントを受け取って処理している点です。

ブラウザを2つ立ち上げて同時にページにアクセスすると分かりますが、一方で投稿したテキストは、即座に他方のブラウザに通知されて、新規メッセージがリスト内に表示されます。

helloworld.gif

特徴

データストレージ

GoInstantでは、ストレージは階層的なツリー構造で提供されており、キーを介して各ノードにアクセスできるようになっています。先程のテキスト統合アプリを例に取ると、メッセージレコードもそれらを格納しているコレクションも、すべて一意のキーが与えられることになります。

階層構造のストレージのルートはルームと呼ばれる単位で管理されています。ルームには接続している参加者のリストが格納されており、ここで参加者を管理したり、メッセージの配信先を制限することができます。

// コレクション
var Message = room.key('messages');
// コレクション内のレコード
var message1 = Message.key('id-1453ab8c1c4-000');
// ルームからのパスで指定することも可能
// var message1 = room.key('messages/id-1453ab8c1c4-000')

/* 1) レコードの内容を取得(コールバック関数での呼び出し)*/
message1.get(function(err, record) {
  // ...
});

/* 2) レコードの内容を取得(Promise形式)*/
message1.get().then(function(record) {
  // ...
}).catch(function(err) {
  // エラー処理
});

後で述べるように、ルーム及びそれぞれのキーに対して、細かいアクセス制御の設定をすることが可能です。

認証

ソーシャルログイン

GoInstantは、FacebookTwitterといった外部アイデンティティプロバイダを利用する、いわゆるソーシャルログインに対応しています。現在サポートされているプロバイダは上記の他にGoogle, Salesforce, GitHubなどがあります。

JavaScript開発者は、特に連携のためのやりとりを気にする必要はなく、 connection.loginUrl(providerName) でプロバイダへのログインURLを取得し、画面をリダイレクトするだけで大丈夫です。

なお、連携するプロバイダについては、あらかじめOAuthのclient_idおよびclient_secretなどを管理設定ダッシュボード画面に登録する必要があります。

JSON Web Token によるアサーション

しかしながら、GoInstantがこういった外部のアイデンティティプロバイダを利用したソーシャルログインに対応したのは実はごく最近で、それまではJSON Web Token(JWT)を用いたアサーションを外部アプリケーションに発行してもらい、それを受け取ってユーザを認証していました。現在も引き続きJWTを利用してカスタムで認証を行うこともできます。

GoInstantが他のBaaSと比べて特徴的なのは、自分自身ではユーザ認証のためのレポジトリを持っていない、ということです。多くのBaaSでは自前でユーザレポジトリを管理し、ユーザ名・パスワードなどのログイン機能を持っていたりしますが、GoInstantでは認証結果を外部プロバイダor外部アプリからアサーションという形で受け取るフェデレーションが前提となっています。このため、既に独自のユーザ空間を持っているWebアプリやWebサイトであっても、プラットフォーム間のユーザ管理の違いに頭を悩ませることなく利用できるようになっています。

アクセスコントロール

BaaSにおいては、エンドユーザから直接データストレージへのアクセスを許すことになるため、データのアクセスコントロールを設定できることは最重要です。また、そのアクセスコントロールのポリシーをどれだけ細かい粒度で設定できるか、ということは、そのプラットフォーム上で利用できるアプリケーションの多彩さを決定づけることになります。

GoInstantでは、アクセス主体をユーザおよびグループ単位で指定できるようになっており、対象のリソースはルームはもちろん、それぞれのキーレベルについても指定可能です。許可する操作についてもかなり細かく(read/writeだけでなくget/set/add/removeとそれらのイベントに対するアクセスなど)指定できます。

なお、先にも記載したとおり、GoInstant内にはユーザレポジトリは存在しないため、ユーザIDやグループなどの情報は外部のプロバイダが利用しているものをそのまま利用するか、外部アプリケーションがJWTのアサーションに属性情報として渡してやる形になります (例:GitHubでログインした場合はユーザIDは github:23387 のような形になる)

これらのアクセスコントロール情報を指定するACLは、JSON形式で記述し、管理ダッシュボードから設定することができます。以下はその例のうちの1つです。

{
  "$room": {
    "#join": { "users": ["*"] },
    "shoppingcart": {
      "#get": { "groups": ["viewers"] },
      "secret": {
        "#get" {
          "users": ["15"],
          "groups": []
        }
      }
    }
  },
  "#connect": { "users": ["*"] }
}

WebRTC

GoInstantは、今年の初めにWebRTCへの対応を発表しました

もちろん、WebRTCのバックエンドを提供しているサービスは他にもあります(国内だとSkyway WebRTCなどが有名です)が、リアルタイム・メッセージングに関わるプラットフォームを包括的に提供しようという意志を感じますし、次世代のコミュニケーションインフラとしての地位を狙いつつあるようにも見えます。

GoAngular

GoInstantが提供するリアルタイムデータ同期の仕組みは、双方向データバインディングを提供するJavaScriptフレームワークと大変相性が良いため、AngularJSなどと組み合わせることによって威力を発揮します。GoAngularは、GoInstantをAngularJSで利用するための仕組みです。

まとめ

想定されるターゲット

GoInstantは、他の多くのBaaS(Mobile BaaS = MBaaS などとも呼ばれます)とは異なり、モバイルアプリに特化している、というわけではありません。Webサイト/Webアプリに対してGoInstantの提供するJavaScriptを埋め込むだけでリアルタイム・メッセージングのサービスを即座に利用できることを売りとしています。

さらに言うと、特にiOSAndroidなどのネイティブアプリ向けのSDKが用意されているわけではなく、現状では想定もされていない様子です。この辺りはすでにiOS向けのSDKなども用意している競合サービスのFirebaseに少々軍配が上がります。

というわけで、新規のモバイルアプリのバックエンドに利用するというよりは、どちらかというとWebサイトやWebサービスは他のPaaS/IaaSなどのクラウド環境で開発・運用を行った上で、リアルタイム関連の機能のみを外出しして利用するようなケースが一番現実味があるでしょうか。本文中で述べたとおり、自前ではユーザレポジトリを持たずに認証連携をデフォルトとするなど、外部サービスとの共存共栄を重視したサービス・アーキテクチャであることは、このような用途に非常に適していると感じます。

また、サーバプログラムを介さなくても静的Webサイトに組み込めるという特性から、S3、GitHub Pagesなどの静的Webホスティングサービスと組み合わせるのも相性が良さそうです。ブログサービスやニュースサイトにウィジットとして埋め込んで、リアルタイム版DISQUS、のような使い方もあるかもしれません。

参考情報:競合サービス

JSONのBase64 encodeについて

JSON文字列は、配列がルートでなく余計な空白が除去してあれば、'{"'
(\u007b\u0022) から始まるため、Base64エンコードすると

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9

のように、必ずey...で始まる文字列になる。

そのため、eyで始まる文字列を見たらそれはJSON文字列のBase64エンコードなのではと推測する機構が、一部アイデンティティ愛好家には備わっています

SAML統合でSalesforceからAWSのサービスにアクセスする (3)

こんにちは、@stomita です。

SAMLを使ったSalesforceとAWS S3の連携についての記事書いてますが、どうやらこれで最終回を迎えそうです。

前回は設定ばかりでかなりめんどくさかったと思いますが、重要なところ(SalesforceからSAML連携してS3のAPIの呼び出しまで)はだいたい達成できました。

今回は、アプリケーションとして実際に役に立つように、作成したアプリに少しづつ手を加えていきます。内容はかなりJavaScriptがメインになります。

はじめてこの記事にアクセスされた方は、これまでの記事をぜひご覧ください。

それではどうぞ。

続きを読む

SAML統合でSalesforceからAWSのサービスにアクセスする (2)

こんにちは、@stomitaです。

前回につづいて、SAML統合機能を利用してSalesforceからAWSのサービス(S3)を連携する方法について記します。

前回がイントロダクションであったのに対し、今回の内容は実際の設定の手順となります。それなりに間違えないように記述しているつもりですが、不備があったらすいません。

手順の中にSalesforceSAML設定を行う場面がありますが、その際の画面は英語表示に切り替えて設定することを推奨します。というのも、SalesforceSAMLまわりの設定については現時点でかなり誤訳/独自訳が多いので、SAMLの用語を知ってる人は頭がハテナ?になるし、SAML初心者も間違えて意味を認識してしまうおそれがあるからです。

なお、1. から3. までの設定はdeveloperforceの公式Wikiのドキュメントにあるので、こちらの手順でセットアップしている場合は飛ばしても構いません。

それではどうぞ。

続きを読む

SAML統合でSalesforceからAWSのサービスにアクセスする (1)

こんにちは。@stomita (id:shinichitomita) です。
メインのTech Blogをはてなブログに移行して、いちおう初回のエントリです。

今回はSalesforceSAML IdP機能を使って、今年のAWS re:Inventで発表されたAWSのSAML統合機能と組み合わせると結構おもしろいことができるよ、ということについてお話します。

なお、この記事は、Force.com Advent Calendar 2013 に参加しているっぽいです。

この記事の想定対象読者

SalesforceSAMLシングルサインオンの設定をして、一体何が美味しいのかわからない、という方。
パスワード2つ登録して別々にログインするので別にいいじゃん?という方のためのエントリ。
SalesforceとAWSでクラウドインテグレーション、みたいなことをしたい企業の方々にはおすすめかも。まあそうでもないかも。

この記事で提案したいこと

SAML統合の設定をしておくと、Salesforceにログインしただけで、S3の安価で膨大なストレージが使えるようになるよ!」

この記事で達成できる予定のこと

  • Salesforceにログインし、任意のレコードの詳細ページを表示
  • 詳細ページに埋め込まれたVisualforce画面に、S3のフォルダ内に格納されているファイルがリストされる。
  • 画面にファイルをドラッグ&ドロップすると、S3のフォルダ内にファイルが保管される

この記事で「やらない」こと

「Apexを使ってサーバサイドでS3にファイルを転送」

  • S3にはブラウザから直接ファイルアップロードできるので、中間プログラムを介する必要は特にない。
  • 不必要にサーバサイドを経由するのは、レスポンス的にも帯域的にもよろしくない。
  • そもそもSalesforceが標準で提供する添付ファイル/コンテンツの容量だったり単価だったりが厳しいから、今回の提案がありえるわけです。

SAML連携の設定

実は、AWSとのSAML連携の設定方法は、だいたいここに書いてあるのでした(あれ、もしかしてこの記事いらない?)
http://wiki.developerforce.com/page/Configuring-SAML-SSO-to-AWS

ただし、この設定手順にしたがって設定すると、シングルサインオンのためのSAMLアサーションをAWS Consoleに渡してしまうので、SalesforceからAWS Consoleにログインすることになる。

通常、Salesforce上のアイデンティティが司るものは、管理者のアイデンティティ(開発・情シス)ではなく、エンドユーザ(営業・サポート・マーケティング・経営者)のアイデンティティなので、AWSのConsoleにログインできてもあまり嬉しくはない。

そこで、今回は、SAMLアサーションのコンシューマとなるWebアプリ(Visualforce)を別に作成し、そちらにSAMLアサーションを渡すように設定する。そして、そのWebアプリからSAMLアサーションを使ってS3にアクセスする。

具体的には、SAMLアサーションからSTS経由で一時トークンを取得し、その一時トークンを利用してS3にアクセスするのだけれども、詳しい方法についてはここに記載がある。
http://docs.aws.amazon.com/STS/latest/UsingSTS/CreatingSAML.html

ただし、ここらへんのやりとりは実はすべてAWS JavaScript SDKがやってくれるので、実際はこのSDKSAMLアサーションをわたしてあげるだけでOK。JavaScriptのプログラムからS3のAPIが簡単に利用できる。

(以下、詳しい手順は次回へ!)