[MBaaS][PaaS] Dropbox Datastore APIでオフラインアプリ開発
先日DropboxからDropboxプラットフォームが発表されました。これまでの単純なクラウドストレージサービスという枠を超えてDropboxプラットフォームとして発展する狙いがあるようです。その中でも特に目をひくDatastore APIを試してみます。
Datastore APIは端的に言いますと、これまでのDropboxのクライアントとサーバ間でのファイル同期と同じように構造化データを同期できるようになる仕組みです。Dropboxは持ち前の同期技術というコアコンピタンスをひっさげてMBaaSの領域に参入することによりMBaaS市場に新しい価値を見出そうとしているように見えます。
具体的にどんなものなのか?Dropboxユーザであれば、すぐに試す事ができるDatastore APIを利用したサンプルアプリケーションがこちらにあります。アプリへ移動してOAuthフローの後にタスクをいくつか追加し、ネットワークを切断しオフラインにしてから編集し再びオンラインにするなどして試してみましょう。
オンライン状態でタスクを3つ登録する
オフラインにしてタスクをひとつ削除する
再びオンラインにして画面を更新してもオフライン時の削除が反映されている
ちなみにDropboxのクライアントアプリ(この場合はMac用ネイティブアプリ)がインストールされていない状態で試した場合、オフラインの削除動作はもちろん動作しますが再びオンラインにした場合に変更が反映される事はありませんでした。またlocalStorageを覗いてみてもトークン情報以外保持されていない事からDropboxのクライアントアプリとサーバ上でオン – オフライン間の差分同期を一手に請け負う仕組みとなっていることがわかります。
サンプルアプリのJavascript部分は以下になります。ざっと見ても何かローカルのデータを処理するような記述とDOM処理くらいしか見当たりません。これで非常に面倒な同期処理が実現できています(HTMLやCSSも同封されたものはこちらのJavascript SDKに含まれています)
// Insert your Dropbox app key here: | |
var DROPBOX_APP_KEY = 'xxxxxxxxxxx'; | |
// Exposed for easy access in the browser console. | |
var client = new Dropbox.Client({key: DROPBOX_APP_KEY}); | |
var taskTable; | |
$(function () { | |
// Insert a new task record into the table. | |
function insertTask(text) { | |
taskTable.insert({ | |
taskname: text, | |
created: new Date(), | |
completed: false | |
}); | |
} | |
// updateList will be called every time the table changes. | |
function updateList() { | |
$('#tasks').empty(); | |
var records = taskTable.query(); | |
// Sort by creation time. | |
records.sort(function (taskA, taskB) { | |
if (taskA.get('created') < taskB.get('created')) return -1; | |
if (taskA.get('created') > taskB.get('created')) return 1; | |
return 0; | |
}); | |
// Add an item to the list for each task. | |
for (var i = 0; i < records.length; i++) { | |
var record = records[i]; | |
$('#tasks').append( | |
renderTask(record.getId(), | |
record.get('completed'), | |
record.get('taskname'))); | |
} | |
addListeners(); | |
$('#newTask').focus(); | |
} | |
// The login button will start the authentication process. | |
$('#loginButton').click(function (e) { | |
e.preventDefault(); | |
// This will redirect the browser to OAuth login. | |
client.authenticate(); | |
}); | |
// Try to finish OAuth authorization. | |
client.authenticate({interactive:false}, function (error) { | |
if (error) { | |
alert('Authentication error: ' + error); | |
} | |
}); | |
if (client.isAuthenticated()) { | |
// Client is authenticated. Display UI. | |
$('#loginButton').hide(); | |
$('#main').show(); | |
client.getDatastoreManager().openDefaultDatastore(function (error, datastore) { | |
if (error) { | |
alert('Error opening default datastore: ' + error); | |
} | |
taskTable = datastore.getTable('tasks'); | |
// Populate the initial task list. | |
updateList(); | |
// Ensure that future changes update the list. | |
datastore.recordsChanged.addListener(updateList); | |
}); | |
} | |
// Set the completed status of a task with the given ID. | |
function setCompleted(id, completed) { | |
taskTable.get(id).set('completed', completed); | |
} | |
// Delete the record with a given ID. | |
function deleteRecord(id) { | |
taskTable.get(id).deleteRecord(); | |
} | |
// Render the HTML for a single task. | |
function renderTask(id, completed, text) { | |
return $('<li>').attr('id', id).append( | |
$('<button>').addClass('delete').html('×') | |
).append( | |
$('<span>').append( | |
$('<button>').addClass('checkbox').html('✓') | |
).append( | |
$('<span>').addClass('text').text(text) | |
) | |
) | |
.addClass(completed ? 'completed' : ''); | |
} | |
// Register event listeners to handle completing and deleting. | |
function addListeners() { | |
$('span').click(function (e) { | |
e.preventDefault(); | |
var li = $(this).parents('li'); | |
var id = li.attr('id'); | |
setCompleted(id, !li.hasClass('completed')); | |
}); | |
$('button.delete').click(function (e) { | |
e.preventDefault(); | |
var id = $(this).parents('li').attr('id'); | |
deleteRecord(id); | |
}); | |
} | |
// Hook form submit and add the new task. | |
$('#addForm').submit(function (e) { | |
e.preventDefault(); | |
if ($('#newTask').val().length > 0) { | |
insertTask($('#newTask').val()); | |
$('#newTask').val(''); | |
} | |
return false; | |
}); | |
$('#newTask').focus(); | |
}); |
これによって以下の恩恵が得られる事がわかります。
- オン – オフライン状態を気にする事無くアプリケーション操作が可能
- 色々なデバイスから行った変更の同期処理を実装する必要がない
- サーバサイドを永続的なデータ保持の用途のみと割り切ればHTML/CSS/Javascriptのみで動的サイトが実現可能(つまり従来のDropboxで静的ページを公開する手法と合わせればDropboxプラットフォームだけで動作するアプリを構築可能)
- 開発側がストレージを用意するのではなくユーザ所有のストレージにデータ保管できる
今はオフラインファーストの観点や煩雑な同期処理の丸投げに視点が集中していますが、4番目の恩恵はなかなか新しい価値であり将来どのような体験へ変貌していくのか非常に楽しみな事項です。これまではベンダー側がデータ領域を用意して、そこにアプリケーションのデータを保持するのが常識のように思われていましたので、このパラダイムシフトが起これば面白そうです。
ユーザ所有のストレージとはDropboxの自身のデータ領域を指しますが漠然としてて解らないという方は自身のApp ConsoleのBrowse datastoresを見てみましょう。先ほど試したタスク管理サンプルアプリ(Tasks Example)が表示されていると思います。このアプリのDatastoreのBrowseをクリックしてみます。
すると以下の画像のようにタスク管理アプリに登録した自身が所有するデータを閲覧できます。
これで自身の所有データという意味がよくわかると思います。MBaaSに限らずクラウドプラットフォームにはデータの所有やアクセスコントロールという概念がつきまとうものですが、Dropboxが提唱する新しいデータのあり方が将来的にどのようなサービスを実現し、どのように常識を覆すのか(もしかしたら覆らないかもしれないけど)個人的には非常に楽しみです。
ちなみにApp Consoleで自身のDropboxアプリも作成可能です。先ほどのサンプルアプリケーションのAppKeyに自身のアプリケーションIDを入力してDropbox上でHTMLを公開して動作させればサーバ要らずでタスク管理アプリが動かせます。また、自身のアプリケーションのDatastoreをBrowseしてみるのも良いでしょう。