atomエディタのソースコードを再利用してアプリのカスタマイズ性を高めよう

本記事はElectron Advent Calendar 2016 5日目の記事です。

InkdropというElectron製ノートアプリを作っています。
このアプリにはプラグイン機構による拡張性を備えているのですが、これはatomからコードを拝借して実装しました。
atomはMITライセンスによるオープンソースのテキストエディタです。
その際に得た知見を共有したいと思います。
これをきっかけにあなたのElectronアプリ改善のお役に立てれば幸いです。
atomのパッケージを作ったことのある方ならすんなり理解できると思います。

続きを読む atomエディタのソースコードを再利用してアプリのカスタマイズ性を高めよう

Markdown-Itで独自レンダリングする方法

軽量でPluggableなJS製Markdownパーサ&レンダラのMarkdown-It
例えばaタグは全てtarget="_blank"にしたいなどの要求を実現するにはレンダラの処理を一部変更する必要がある。
その変更方法について説明する。

md.renderer.rulesを書き換える

以下のようにlink_openという名前のルールを書き換えることで、target="_blank"をデフォルトに出来る。

続きを読む Markdown-Itで独自レンダリングする方法

ElectronでデスクトップからD&Dでファイルを受け取る方法

FinderやエクスプローラなどからElectronアプリケーションにファイルをドラッグ&ドロップする操作を実現する方法について説明する。
すごく簡単。

まず以下のようにデフォルトの挙動をキャンセルする。

document.ondragover = document.ondrop = function (e) {
  e.preventDefault()
}

続きを読む ElectronでデスクトップからD&Dでファイルを受け取る方法

XCode 7を入れたらnode-gypが動かなくなった件

npmを実行すると以下のエラーを得る:

ld: library not found for -lgcc_s.10.5
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [Release/.node] Error 1

gccなのでXCodeが関連している模様。
関連情報:

I got the same error here, a possible error after upgrading xcode version.
I fix it with a temp solution:
cd /usr/local/lib sudo ln -s ../../lib/libSystem.B.dylib libgcc_s.10.5.dylib

とりあえず以下で対処できるらしい:

cd /usr/local/lib;
sudo ln -s ../../lib/libSystem.B.dylib libgcc_s.10.5.dylib 

んーむ・・

一人でWebサービス作るならnode.jsがオススメという話

「好み」や「慣れ」「制約」の問題は忘れて読んで欲しい。それは個々人の問題だから。

時代はSPA

スマホの普及で、モバイルネットワークなのに画面遷移ごとにページのリロードなんてユーザはじれったくて待ってくれない。
だから、SPA(Single Page Application)という手法が出るのは必然だ。
これは、画面遷移に応じて必要なデータを読み込み、必要な部分だけを再描画する手法だ。

言語のスイッチングコストは予想以上に高い

昔と比べて、ウェブサービスの開発コストはほんとうに下がった。
だから、一人でサービスを作る人もこれから格段に増えるに違いない。

一人でSPAを組む時にしんどいのが、言語のスイッチングコストだ。
Railsで組んでいるなら、RubyとJavaScriptの2つを並行して書かなければならない。

言語はそれぞれ独特の作法があり、いいコードを書くにはそれなりの鍛錬が必要だ。
しかしいかに熟練していたとしても、常に複数言語のテクニックやライブラリにキャッチアップするのは大変だ。

node.jsは”お一人様向け”

node.jsは、サーバサイドで動くJavaScript環境だ。
つまり、スイッチングコストが圧倒的に低い。

もちろん、DOM操作やファイル操作など、クライアントとサーバサイドでは相互で通用しないテクニックもある。
でもそれは言語の問題ではなく、アーキテクチャの問題だ。

一人で効率よく開発したいなら、node.jsをお薦めする。
フレームワークはどれがいい?
そんなのは今どきどれもよく出来ているから、「住めば都」だ。

[node.js] mongooseでクエリをタイムアウトさせるには

MongoDB

tl;dr

  • MongoDBではv2.6から cursor.maxTimeMS()によって処理をタイムアウトさせられる
  • 各ドライバから使用可能
  • mongooseでは mongoose.Query#maxTime メソッドで指定可能

サーバ側で処理をタイムアウトさせるには

MongoDBでは、v2.6からサーバ側で処理の実行タイムリミットを設定できます。
処理のタイムリミットは、cursor.maxTimeMS() メソッドを呼ぶことで設定できます。
この maxTimeMS を設定すると、指定したタイムリミットを超えて検索やアップデートが実行された場合、その処理を中止します。

以下のようにコンソールで試せます:

db.collection.find({description: /August [0-9]+, 1969/}).maxTimeMS(50)

各ドライバでのmaxTimeMSの使用方法

各ドライバといっても調べたものだけを列挙しておきます。

PHP

例:

$cursor = $collection->find();
$cursor->maxTimeMS(2000);

try {
    $results = iterator_to_array($cursor);
} catch (MongoExecutionTimeoutException $e) {
    echo "query took too long!";
}

ちなみに、PHPにはMongoCursor::timeout もあります。
このメソッドはクライアント側でタイムアウト処理を行うものです。
サーバ側では処理をキャンセルしないので、注意です。

node.js

こちらに詳しく書いてあります。
例:

var MongoClient = require('mongodb').MongoClient;

MongoClient.connect("mongodb://localhost:27017/test", function(err, db) {
    // Get an aggregation cursor
    var cursor = db.collection('data')
        .find({"$where": "sleep(1000) || true"})
        .maxTimeMS(50);

    // Get alll the items
    cursor.toArray(function(err, items) {
        console.dir(err);
        console.dir(items);
        db.close();
    });
});

mongooseでタイムアウトさせるには

MongoDBのnode.jsにおけるODM(Object Data Mapping)のmongooseでもmaxTimeMSに対応しています。
v3.8.13 から利用できるようになりました。
該当の変更は こちら
実際には子分モジュールの mquery で実装されています。

使い方はシンプル:

var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');

var Cat = mongoose.model('Cat', { name: String });
Cat.find({name: 'Zildjian' }).maxTime(1000).exec(function(err, docs) {
    console.log(err); // --> { [MongoError: operation exceeded time limit] name: 'MongoError' }
});

operation exceeded time limit というメッセージのエラーが得られます。

このように、mongoose.Query オブジェクトを返すメソッドでmaxTimeメソッドが使えます。
Model#saveメソッド、Model#remove では残念ながら使えないようです。

エラーを判別しにくい問題

Errorオブジェクトにはエラー種別を明示的に判別するコードがありません。
そのため、とても不安な方法ですがエラーメッセージで判別する方法しか見当たりません(他に良い方法をご存知の方がおられましたらぜひご教授ください)。
メッセージによるタイムアウト判別は以下のようになります:

if(err.message === 'operation exceeded time limit') {
   // retry
}

なぜPHPにはMongoExecutionTimeoutExceptionがあるのにnode.jsでは無いんだ?

参考

[nodejs] DynamoDB ODMのvogelsをJSON形式に対応させた

DynamoDB

DynamoDBがJSONデータをサポートした

今月(2014年10月10日)のことですが、Amazon DynamoDBがいろいろアップデートしました:

その中でもJSONデータサポートは注目のアップデートです。
これは具体的にどういうことか説明します。
DynamoDBはスキーマレスのNoSQLです。
これまで、カラム(属性)には単一データまたはセット(重複を許さない配列)が格納できました。
しかしながら、入れ子構造は格納できませんでした。
JSONデータのサポートとは、この入れ子構造が取り扱えるようになったということです。
つまり、MongoDBのように、JSON形式のデータをそのままデータベースに格納できるようになったのです。

既存ODMライブラリに手を加えて対応させた

node.js向けのODMライブラリにはいくつかあります。
vogels はその中でもいい感じのライブラリです。
しかし残念ながら、あまり精力的にメンテナンスされてるとは言いがたく、今回のアップデートに対してもまだ対応していない様子です。
そこで、自分で対応させてみることにしました。
フォークしたものがこちらにあります:

いちおうプルリクしてありますが、マージされるのを待てません笑

vogelsの使い方

まずは基本的な使い方をドキュメントから引用してざっくり説明します。
次に、入れ子構造のデータを扱うための方法を説明します。

設定

AWS SDKのキーをファイルから設定します。

var vogels = require('vogels');
vogels.AWS.config.loadFromPath('credentials.json');

もちろん直接値を渡して設定もできます。

var vogels = require('vogels');
vogels.AWS.config.update({accessKeyId: 'AKID', secretAccessKey: 'SECRET'});

基本

データモデルの定義方法は以下のとおりです:

var Account = vogels.define('Account', function (schema) {
  schema.String('email', {hashKey: true});
  schema.String('name').required(); // name attribute is required
  schema.Number('age'); // age is optional
  schema.Date('created', {default: Date.now});
});

ハッシュとレンジキーを指定しての定義:

var BlogPost = vogels.define('Account', function (schema) {
  schema.String('email', {hashKey: true});
  schema.String('title', {rangeKey: true});
  schema.String('content');
  schema.StringSet('tags');
});

書き込み:

Account.create({email: 'foo@example.com', name: 'Foo Bar', age: 21}, function (err, acc) {
  console.log('created account in DynamoDB', acc.get('email'));
});

読み込み:

Account.get('test@example.com', function (err, acc) {
  console.log('got account', acc.get('email'));
});

簡単ですね!
クエリやセカンダリインデックスなどの使い方はドキュメントを参照してください。

入れ子構造の取り扱い方

ここからが本題です。
DynamoDBでは、MapとListという新しいデータ型を追加することで入れ子構造の格納を実現しています。
先述の通り、vogelsではスキーマを定義して、それに合わせたデータを出し入れします。
vogelsで入れ子構造を取り扱うには、このスキーマをMapとListを使用して定義します。

Map

MapはJavaScriptでいうオブジェクトです。まずは以下の例をみてください。

var Photo = vogels.define('Photo', function (schema) {
  schema.String('userid', {hashKey: true});
  schema.Map('location');
});

Photo.create({userid:'john', location: {name: "東京タワー", latitude:35.65858, longitude: 139.745433}}, console.log);

この例では、Photoモデルのlocation属性がMap型として定義されています。
中身には、場所に関するデータが格納されています。
単純ですね!
もちろん、このlocationの中身もスキーマで定義できます。

var Photo = vogels.define('Photo', function (schema) {
  schema.String('userid', {hashKey: true});
  schema.Map('location', function(schema) {
    schema.String('name');
    schema.String('latitude');
    schema.String('longitude');
  });
});

Mapの中に更にMapを格納することもできます
Mapの中身のスキーマを定義しなかった場合、未定義となり制約は与えられず、自由なフォーマットのデータが格納できます。

List

ListはJavaScriptでいう配列です。使い方は以下のとおり。

var Photo = vogels.define('Photo', function (schema) {
  schema.String('userid', {hashKey: true});
  schema.List('likes');
});

Photo.create({userid:'john', likes: ['emily']}, console.log);

こちらも直感的ですね。
Listの各要素の形式が同じで尚且つMap型の場合は、以下のように要素のスキーマを定義できます。

var Photo = vogels.define('Photo', function (schema) {
  schema.String('userid', {hashKey: true});
  schema.List('likes', function(schema) {
    schema.String('userid');
    schema.Date('likedAt');
  });
});

MongoDBとの併用はかなりイケてる?

JSON形式のドキュメントがそのまま格納できるDBMSとして、有名なのはMongoDBです。
今回のJSONサポートによって、DynamoDBはMongoDBと同じフォーマットでデータを取り扱えるようになりました。
これは、「併用しやすい」という事です。
併用すると何が嬉しいか、考えてみます。

DynamoDBは運用コストが低くスケーラビリティが高いのがウリのデータベースです。
その代わり、検索の柔軟性を犠牲にしています。
それに対してMongoDBは検索の柔軟性が高いのが強みです。
しかしながら、DaaSは価格が高いし自分で運用するのは大変です。
DynamoDBとMongoDBを併用することによって、互いの弱みを補い合えるかもしれません。

MongoDBを既に使っていた人にとっては、すごい良いニュースではないでしょうか。