読者です 読者をやめる 読者になる 読者になる

$shibayu36->blog;

プログラミングの話や自分の考えを色々と書いています。特にperl、emacsや読んだ本の話が多いです。

JavaScriptのPromiseの概念について学習した時に参考にしたもの

tech javascript

JavaScriptのPromiseを使った実装をコードレビューしていたのだけど、やってみたら自分があんまりPromiseについて理解できていなかったことに気づいた。特にこれまでjQueryajaxjQuery Deferredをなんとなく使っていたのだが、Promiseの根本的な概念がjQueryの巨大な仕様の中に埋もれていて、自分の中でjQueryの機能とPromiseの概念を分離して正しく言語化出来ていなかった。

そこでPromiseの根本的な概念を再学習したので、何を参考に勉強したかを書いておく。


PromiseについてはとにかくJavaScript Promiseの本を読めば大体把握できる。このドキュメントを読めば

  • Promiseの概念
  • ES6 Promiseの利用方法
  • Deferredとの関係性

などといったことを知ることができる。

個人的には次の部分が特に参考になった。

  • thenでもrejectする
    • thenに渡すcallbackで返す値がPromiseの時にどうなるか説明されている
  • DeferredとPromise
    • DeferredとPromiseの関係性が説明されている
    • Deferredの簡易実装が紹介されているので非常に理解しやすい


これを読んだ後、爆速でわかるjQuery.Deferred超入門 - Yahoo! JAPAN Tech Blogを読みなおした。これを読むときは、先ほどのドキュメントで学んだPromiseの根本的な概念とjQueryのDeferredの機能との対応を意識しておくと、理解しやすかった。


大体この二つのドキュメントを読んだらPromiseについて再学習することが出来た。さらに学びたければ自分でPromiseの簡易実装をしてみると良いかもしれない。

WEB+DB PRESS vol.94で「Perl開発への動的な型制約の導入」について執筆しました

tech

 WEB+DB PRESSPerl Hackers Hubで執筆しませんかとお声がけいただいたので、「Perl開発への動的な型制約の導入」について執筆しました。本日発売です。

WEB+DB PRESS Vol.94

WEB+DB PRESS Vol.94

 今回は、動的な型制約とは何かや、なぜPerlに導入したいのか、Smart::Argsを使った型制約の導入方法、型制約の応用など、動的な型制約にまつわる内容について書きました。Perlでもっと安全に開発したいと思っている方には参考になると思うので、是非見ていただければと思います。見出しは次のとおりです。

  • なぜ動的な型制約を導入したいのか
  • Smart::Argsで引数に型制約を導入する
  • Mouse::Util::TypeConstraintsで独自の型を定義する
  • 動的な型制約の応用例
  • 動的な型制約を導入してみて


 他にも実践スケーラブルAWSや作って学ぶElectronなど、非常に面白そうな特集があるので、購入して見ていただけるとありがたいです!


 WEB+DBへの執筆は初めてで、また8ページという長さの文章はこれまで書いたことがなかったので、想像していたよりも苦労しました。しかし、それにより新しく執筆という経験が出来て非常に良かったです。また機会があったら何か執筆をしてみたいです。

PerlでHarrietを使ってElasticsearchのテストをする & 並列でもテストできるように

tech elasticsearch

 Elasticsearchを使った開発をしていると、Elasticsearchを使った機能でも簡単にテストしたいという気持ちになってくる。またproveの-jオプションを使って並列に動かしていても変にコンフリクトせずにいい感じになってほしい。

 この課題を解決するために、Harrietを用いてテスト実行前にElasticsearchを起動し、テストを行うというのをやってみた。

作戦

  • Harrietを用いて、テスト実行前にElasticsearchのnodeを一つ起動する
  • 並列数分、Elasticsearchのインデックスを作成し、ついでにロック用のインデックスも作っておく
  • 環境変数で情報をテストファイルに渡す
  • テストファイルでは環境変数から情報を読み込んで、ロックを取りつつ、立ち上がったportやインデックスにアクセスする

HarrietでElasticsearchの起動 & インデックスの作成

 HarrietについてはHarriet ー テストのときつかうにデーモンの取扱を簡単にするためのフレームワーク - tokuhirom's blog を参照。

 t/harriet/elasticsearch.plを用意して、以下のように書く。

use strict;
use warnings;
use utf8;

use JSON::XS qw(encode_json);
use Test::TCP;
use App::Prove;

use Search::Elasticsearch;

# ctrl-cした時とかにguardが開放されないのでここで明示的に開放してkillしている
# See also: http://perl-users.jp/articles/advent-calendar/2012/hacker/9
sub install_signal_handler ($) {
    my ($sig) = @_;

    my $original_handler = $SIG{$sig};
    $SIG{$sig} = sub {
        $HARRIET_GUARDS::ELASTICSEARCH = undef;
        $SIG{$sig} = $original_handler || 'DEFAULT';
        kill $sig => $$;
    };
}

# テスト用にElasticsearchを一台立ち上げる
my $now = time;
my $test_process_pid = $$;
$HARRIET_GUARDS::ELASTICSEARCH = Test::TCP->new(
    code => sub {
        my $port = shift;

        # elasticsearchのtcp側は大体100増やしたportを使うので、それに合わせる
        my $tcp_port = $port + 100;
        exec(
            'elasticsearch/bin/elasticsearch',
            # テストが同時に実行された時にそれらで勝手にクラスタを作られると困るので
            # ジョブのプロセスIDをクラスタ名に入れる。またpidだけではいつか
            # 被る恐れがあるので、現在時刻もクラスタ名に入れる
            sprintf("--cluster.name=sample-test-%s-%s", $now, $test_process_pid),
            "--http.port=$port",
            "--transport.tcp.port=$tcp_port",
        );
    },
);
my $http_port = $HARRIET_GUARDS::ELASTICSEARCH->port;

# インデックスの作成
my $es = Search::Elasticsearch->new(nodes => ["localhost:$http_port"]);
my $elasticsearch_index_names = [];

my $setup = sub {
    my $j = shift;

    my $index_name = "sample-$j";

    # テストで利用するインデックスを作成。これは別ファイルで
    # Mappingsを定義しておいて流しこむのが良いと思う
    $es->indices->create(
        index => $index_name,
        body => {
            mappings => {
                hoge => {
                    properties => {
                        foo => { type => 'string' },
                    },
                },
            },
        },
    );

    # 並列時にロックを取るためのMappingを用意
    $es->indices->put_mapping(
        index => $index_name,
        type  => 'lock',
        body  => {
            lock => {},
        },
    );

    push @$elasticsearch_index_names, $index_name;
};

# テストの並列数分インデックスを作る
my $prove = App::Prove->new;
$prove->process_args(@ARGV);
my $jobs_n = $prove->jobs || 1;
$setup->($_) for (1..$jobs_n);

# INTとTERMのときに正しくguardが開放されるようにする
install_signal_handler($_) for qw(INT TERM);

# 環境変数を経由して、portと作成したインデックス名をテストに渡す
$ENV{TEST_HARRIET_ELASTICSEARCH_JSON} = encode_json({
    http_port   => $http_port,
    index_names => $elasticsearch_index_names,
});

 すごく長いし難しいのだが、それぞれやっていることはコメントに書いた。ポイントとしては

  • 並列に実行するとして、その台数分Elasticsearchを起動するのは重いので、インデックスを複数作るという方向で考える
  • Elasticsearchはクラスタを勝手に作るので、クラスタ名が被らないように工夫する
  • $HARRIET_GUARDSというところに入れておけば、テスト終了時にオブジェクトが破棄され、Elasticsearchを終了できる

テストファイルから起動したElasticsearch及び作成したインデックスにアクセスする

 環境変数で情報は渡ってきているので、それをテストファイルから利用したら良い。

 例えば以下のようにt/elasticsearch.tを作る。

use strict;
use warnings;
use utf8;

use lib 't/lib';

use Test::More;
use Search::Elasticsearch;
use JSON::XS qw(decode_json);
use Guard qw(guard);

my $http_port;
my $free_index_name;
my $elasticsearch_unlock;

if (my $elasticsearch = eval { decode_json($ENV{TEST_HARRIET_ELASTICSEARCH_JSON}) }) {
    $http_port = $elasticsearch->{http_port};

    # ロック用のドキュメントID。どんな値でも良いので1にしている。
    my $LOCK_ID = 1;

    # Harrietで作ったインデックスに対してロックが取れるまで待つ
    # 並列数分インデックスは作成しているので、一瞬で取れる想定
    my $es = Search::Elasticsearch->new(nodes => ["localhost:$http_port"]);
    GET_INDEX: for my $count (1..100) {
        for my $index_name (@{ $elasticsearch->{index_names} }) {
            eval {
                $es->create(
                    index => $index_name,
                    type  => 'lock',
                    id    => $LOCK_ID,
                    body  => {},
                );
            };
            unless ($@) {
                $free_index_name = $index_name;
                last GET_INDEX;
            }
        }

        # 取れなかったら1秒待つ
        sleep(1);
    }

    die 'No elasticsearch index acquired' unless $free_index_name;

    # オブジェクトが破棄されたらロックが解除されるようにしておく
    $elasticsearch_unlock = guard {
        my $es = Search::Elasticsearch->new(
            nodes  => ["localhost:$http_port"],
        );

        $es->delete(
            index => $free_index_name,
            type  => 'lock',
            id    => $LOCK_ID,
        );
    };
}

my $es = Search::Elasticsearch->new(nodes => ["localhost:$http_port"]);
ok $es->indices->exists(index => $free_index_name), 'index exists';

undef $elasticsearch_unlock;

done_testing();

 これはベタッと書いてみたけど、あとはこのロックを取る部分をメソッド化してあげて、返り値でguardオブジェクトやportや取得したインデックス名を受け取れば、いろんなテストで利用できるはず。