昔書いたGithub止まりのモジュールをほじくり返す – Test::Proc 篇

※このエントリはPerlアドベントカレンダー2018の5日目のエントリとなります。

CPAN(MetaCPAN)でモジュールを公開するということ

Perlでプログラムを書くことに慣れてくると、CPANモジュールを使うようになったり、もっと慣れてくると自分でPerlモジュールを作るようになり、最終的にはCPAN(今はMetaCPANですね)で公開するようになるかもしれません。私は過去にそのような道を歩んできました。

では私は過去にどんなモジュールを書いたのか。ひとまずMetaCPANに上がっているものを一部挙げると、 Otogiri(これはほとんど@tsucchiさんの力です), XML::Diver, Net::Azure::CognitiveService::Faceなどがあります。

PerlモジュールをMetaCPANで公開する前に

しかしながら、MetaCPANでモジュールを公開する前に気にかけておいて欲しいこととして、個人的には以下のような項目を挙げたいと思います。

  • そのモジュールを公開することで不快感を感じる人々が生じないか
  • そのモジュールはセキュリティ的に致命的な問題を抱えていないか
  • そのモジュールは新しい機能を提供してくれるか
  • そのモジュールはプログラミング体験をより幸せにしてくれるか
  • そのモジュールは従来の類似モジュールよりもパフォーマンスに優れるか
  • そのモジュールは従来の類似モジュールよりも依存性が削減されているか

もし上記について該当するかわからない場合は、ひとまずgithubに上げておきつつ、ブログやTwitter等で言及してみましょう。運が良ければ誰かが注目してくれて、レビューをしてくれるかもしれません。これらのことは、Perlモジュールの公開に及び腰になってしまわない程度に、気をつけるとよいのではないでしょうか。

CPANの歴史を少しだけ垣間見る

では、なぜこのように自律する必要があるのでしょうか。それを知るためには過去に目を向けることが重要です。

歴史を振り返ると、charsbarさんによるエントリでCPANは幼稚園児の砂場じゃないよねという、2008年当時、大変考えさせられたものがあります。若い方の中にはこのような歴史を知らない方もいると思いますが、是非とも同じ轍を踏まぬための知識として、知っておいていただければ幸いです。

また、まかまかさんによるPerl同人誌「Acme大全」には、「Acme大全2012」から毎回、巻末付録として「とあるAcmeモジュールの削除について」という章が設けられています(ブログにも同様のエントリがある)。

このように、MetaCPANへモジュールを公開する上では、ある程度その性質を律せられる側面があることは間違いないでしょうし、誰かがつらい思いをしないためにも必要なことであると私は思います。

それでも日の目を見ないモジュールもある

そんな事もあって、実際にはたくさんのPerlモジュールを作っていたとしても、実際にMetaCPANにて公開されるものはその一部でしかありません。ほかにもMetaCPANに公開するのが怖いとか、作者がそこまでの有用性を感じていないだとか、謙遜している、などの理由でMetaCPANで公開されていないPerlモジュールがGithubにはあります。

このようにあえてGithubでの公開にとどめられているPerlモジュールを、私は親しみを込めてGithub止まりモジュールと呼んでいます。

そして今回紹介するGithub止まりモジュールは、拙作のTest::Procです。

Test::Proc – プロセスを起動し、その起動状態をテストする

Test::Procは5年前にGithubにpushされています。今となっては当時の記憶を頼りに解説する他ないという点がいささか不安です。

さて、Test::Procが目指したところは、テストスクリプトでプロセスの起動状態をテストしたいというものでした。

使い方はSYNOPSISにある通りです。(done_testingが抜けていたので、補足してあります。)

use Test::More;
use Test::Proc;


my $proc = test_proc 'my_task' => sub {
    print 'test';
    warn 'dummy';
    sleep 20;
};


$proc->is_work;


$proc->stdout_like( qr/\Atest/ );
$proc->stderr_like( qr/\Adummy/ );


$proc->exit_ok;
done_testing;

あら捜し

レポジトリを見てみると、テストの少なさが気になります。github止まりモジュールにはよくある話かもしれないですが、だいぶ控えめなテストの数だなと感じますね。

そしてインターフェースが片手落ちです。というのも、このTest::Procはtest_procというDSLを提供しており、Test::Proc::Objectインスタンスを返しているのですが、test_proc側にはクロージャ以外でプログラムを起動する方法が提供されていません。Test::Proc::ObjectはProc::Simpleのサブクラスであり、24行目で引数の$codeを実行しているので、Proc::Simpleのインターフェースを見る限り、文字列で外部コマンドとオプションを渡すことができるはずです。

実験

実際に以下のようなテストを作って実験してみました。

$ cat hoge.sh 
sleep 3
echo "hoge"

$ cat t/12_shell.t 
use strict;
use Test::More;
use Test::Proc::Object;

my $proc = Test::Proc::Object->new('sleep 3 sec. Then print "done"', 'bash ./hoge.sh');

$proc->is_work;
$proc->exit_ok;
$proc->isnt_work;

$proc->stdout_like(qr/\Adone\z/);
$proc->stderr_like(qr/\A\z/);

done_testing;

これを実行すると、以下のようになり、テストは成功します。

$ perl -Ilib t/12_shell.t 
ok 1 - process sleep 3 sec. Then print "done" is work
ok 2 - process sleep 3 sec. Then print "done" exit code is 0
ok 3 - process sleep 3 sec. Then print "done" is not work
ok 4
ok 5 - process sleep 3 sec. Then print "done" STDERR like (?^:\A\z)
1..5

深掘りしていく

上記のことから、test_procにコマンドを文字列渡しして起動させることができそうですが、実際に以下のようなテストを試してみると、失敗してしまいます。

$ cat t/13_shell-with-test_proc.t 
use strict;
use Test::More;
use Test::Proc;

my $proc = test_proc 'my_work' => 'bash ./hoge.sh';

$proc->is_work;
$proc->exit_ok;
$proc->isnt_work;

$proc->stdout_like(qr/\Adone\z/);
$proc->stderr_like(qr/\A\z/);

done_testing;

$ perl -Ilib t/13_shell-with-test_proc.t 
Type of arg 2 to Test::Proc::test_proc must be sub {} (not constant item) at t/13_shell-with-test_proc.t line 5, near "'bash ./hoge.sh';"
Execution of t/13_shell-with-test_proc.t aborted due to compilation errors.

これはtest_procのインターフェースが($&)となっているために起こっている問題となります。ですのでこれを($$)としてあげると、以下のように成功します。

$ perl -Ilib t/13_shell-with-test_proc.t 
ok 1 - process my_work is work
ok 2 - process my_work exit code is 0
ok 3 - process my_work is not work
ok 4
ok 5 - process my_work STDERR like (?^:\A\z)
1..5

なぜgithub止まりなのか

このように、完成度の低さゆえにgithub止まりとなっているTest::Procですが、その他に大きな理由として、私個人が出くわす状況にそもそもテストスクリプトでプロセスの起動状態をテストしたいケースがあまりにも少ないという点が挙げられます。

そういうわけで、結局あまり洗練もされないまま5年もgithub止まりとして塩漬けになっていたというわけです。

Test::Procの今後

一応、ユースケース的に「いいじゃん?」って思ってくれる人がいらっしゃるのであれば、forkしてshipitするまでやってくだされば良いとおもいますが、今の所私自身がなにかをするということは無いでしょう。

コンセプトモデルということでここはひとつ、ご理解いただければ幸いです。

まとめ

ざっくりまとめると以下のようになります。

  • MetaCPANでPerlモジュールを公開する前に、いま一度そのモジュールの性質を見つめ直そう
  • Perlコミュニティの歴史から、誰かがつらい思いをしないための行いについて知ろう
  • Test::ProcというPerlモジュールを作ったけど、ユースケースに遭遇しなさすぎて結局Github止まりになった
  • Github止まりモジュールは「コンセプトモデル」として見ると良さそうかも

明日のPerlアドベントカレンダーは6日目。@yoku0825さんによる「マイエスキューエルにはPerl Mongerが必要かもしれないはなし」です。

Twitterアカウントを凍結されてしまいました。

様子です

しばらくはこちらのサブアカウント にて細々とつぶやいております。

ちなみに本日は私の誕生日でした。最高のプレゼントをありがとう、Twitter!

状況

Azure関連でちょっとした疑問があったので、 @AzureSupport に質問をしたところ、

と言われました。

言われたとおりに質問内容と、サポートに必要なID情報をDirect Messeageにて伝えたところ、suspendedとなってしまいました。

見解

今回suspendedとなってしまった原因は “Violating our rules against evading permanent suspension.” だそうです。

原因については「なるほど?」という感じですが、そもそも今回の経緯から、Twitter社はDirect Messageを覗き見しているという事がわかりました(利用規約にはその旨が書かれている)。

おそらくコンテキスト等を無視して、固有情報らしきものが流れてきたら簡単なチェックを経て、必要に応じてsuspendしているのではないでしょうか。

今後

不服申立てはすぐに行いました。が、仮にアカウントが復活しても、あまりアクティブな利用を行わず、そのままmastodonあたりに流れるかもしれません。

翌日

アカウントの解凍が行われました。Twitter社の担当部門の皆様、大変お手数をおかけしました。

Twitter社においては、認証済みアカウントとのやり取りにおける秘匿情報の取扱いについて、検閲・凍結のルールを見直していただくことを願う次第です。

200ミリ秒の検索APIをMicrosoft Azureでデザインする

200ミリ秒の検索APIとは

ここでは、httpクライアントからリクエストを受けてから全文検索を含む何らかの検索処理を行い、httpクライアントにレスポンスを返すまでの処理を、おおむね200ミリ秒(200ms、0.2秒)前後の時間でこなすWeb APIを指します。

検索という機能を考えたとき、200msという数字はまずまず軽快な応答速度ではないかと私は考えます。

今回はAzureの各種サービスを使って検索APIを作った時のノウハウを共有します。このAPIはデータサイズによって多少のばらつきはあるものの、ほぼ200ms前後の応答速度を実現することができております。

以下は、実際の処理履歴となります。おおむね200ms前後で処理が完了し、応答していることが分かると思います。

ログ

Microsoft Azureで作る

Microsoft Azure(以下Azure)で検索APIを作るにあたり、以下のようなシステムデザインを行いました。

システムデザイン

ある程度Azureに詳しい方であれば、上記の図を見ただけで概ねの構成がご理解いただけると思います。

Azure CDN

いわゆるCDN(Contents Delivery Network)サービスです。今年から動的コンテンツの高速化にも利用できるようになりました。

今回のケースでも「動的コンテンツの高速化」がその利用目的となります。

Azure Functions

実際にhttpリクエストを受け付け、httpレスポンスを返すためのアプリケーション・ロジックをデプロイし、運用するためのFaaSとなります。最近v2がリリースされましたが、私のケースではv1を利用しました。

C#やF#、PHPなどの言語に対応していますが、今回はNode.jsをつかって作ってみました。実際の実装・はまりどころについては前回のエントリにまとめてありますので、そちらも併せてご参照ください。

Azure Search

Apache LuceneクエリやODataフィルタを利用可能な検索エンジンサービスです。

全文検索や緯度経度をもとにした距離検索、ファセットなどにも対応しているため、複雑な検索機能を実装するにはほぼ必須となります。

また、Azure CosmosDBやAzure Table Storageなどから定期的(最短で5分おき)にデータを取り込むIndexerという仕組みがついてきますので、検索結果にリアルタイム性を求めない限り、データのインポートをする仕組みを自分で作る必要がありません。

Azure CosmosDB (または Azure Table Storage)

どちらもスキーマレスなデータストアとして利用できるサービスです。

今回はAzure CosmosDBを利用し、Azure Search向けの一次データをストックしておくデータストアとします。

まとめ

200ms前後の応答速度の検索APIをAzureで実現するシステムデザインを紹介しました。

「えっ、実際の作り方は書かないの?」という声が聞こえてきそうですが、その辺は公式ドキュメントや有志のブログに書いてあることしかやっていません。

強いてあげるとすれば、前回のエントリで書いたようなはまりどころがあるくらいですので、そちらを見ていただきたいと思います。

参考にしたドキュメント・ブログ

[Azure + javascript]Functionsのhttp triggerな関数でSearchの検索結果を返す

暖簾に腕押し

私がAzure Functionsに提供してほしいと切に願っている機能の一つに、Search Bindings(仮称)があります。なお、私がこの機能実装のリクエストを出しました。

しかし、現時点ではこのような機能が提供される気配は全くなく、付帯するロジックを記述するしかありません。暖簾に腕押し、というやつです。

実際、このようなロジックを書くことすら億劫なのですが、嘆いていても仕方がないですので、javascriptからAzure Searchへ問い合わせを行うnpmライブラリazure-searchを利用し、レスポンスを返すところまで実装することにしました。

役割

  • クライアント : webAPIを利用し、HTTP Responseに含まれた検索結果を受け取ります。

  • Azure Functions : クライアントからHTTP Requestを受け取り、Azure Searchに問い合わせをし、結果をクライアントに返します。

  • Azure Search : クエリをFunctionsから受け取り、検索結果を返します。また、Indexerという機能を利用し、定期的にTable Storageからデータを同期します。

  • Table Storage : 検索対象となるデータがストックされています。Indexerによって、定期的にデータ参照されます。

データ構造

検索対象となるデータ構造は以下のようなものです。

[
  {
    "PartitionKey": "2018-08-29:myroom",
    "RowKey": "ecd53616-e756-41fb-98d2-fe2b387e0c8a",
    "id": "ecd53616-e756-41fb-98d2-fe2b387e0c8a",
    "channel": "myroom",
    "body": "5000兆円 欲しい!!!",
    "author": "ytnobody",
    "visible": true,
    "timestamp": 1535508299
  },
  ...
  ...
]

PartitionKeyおよびRowKeyはいずれもTable Storageで必須の項目です。(参照:Azure ストレージ テーブルの設計ガイド: スケーラブルな設計とハイパフォーマンスなテーブル

Search側のスキーマ構造

インデックスmessageには、Table Storageに格納されているデータ構造を、ほぼそのまま持ってきています。Table Storageで利用していたPartitionKeyおよびRowKeyはここでは使いません。

  • id Edm.String (key, retrievable, searchable)
  • channel Edm.String (retrievable, filterable)
  • body Edm.String (retrievable, searchable)
  • author Edm,String (retrievable, filterable)
  • visible Edm.Boolean (retrievable, filterable)
  • timestamp Edm.Int64 (retrievable, filterable, sortable)

普通に実装

最初、以下のように実装しました。

// function.json
{
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ],
  "disabled": false
}
// index.js

module.exports = function (context, req) {
    // Searchクライアント初期化
    const AzureSearch = require("azure-search");
    const client = AzureSearch({
        url: process.env.SEARCH_URL, 
        key: process.env.SEARCH_KEY
    });

    // 検索ワードをスペースで切って配列にする
    const words = req.query.word ? req.query.word.split(' ') : [];

    // ページング指定
    const page = req.query.page ? parseInt(req.query.page) : 1;
    const top  = req.query.size ? parseInt(req.query.size) : 10;
    const skip = top * (page - 1);

    // 検索オプション
    const search = words.map(w => `body:${w}`).join(' AND ');
    const filter = 'visible eq 1';
    const searchOptions = {
        queryType:  "full",
        searchMode: "all",
        top:        top,
        skip:       skip,
        search:     search,
        filter:     filter
    };

    // 問い合わせ
    client.search('message', searchOptions, (err, results) => {
        context.res = err ? {
            status:  500,
            headers: {"Content-type": "application/json"},
            body:    {"message": `Internal Server Error: ${err}`}
        } :
        {
            status:  200,
            headers: {"Content-type": "application/json"},
            body:    results
        };
        context.done();
    });
};

察しの良い方なら気づいたかもしれませんが、このロジックは期待通りには動かず、502エラーを返してしまいます。

何がダメなのか

期待通りに動かない原因は、client.search(...)の結果を受け取る前にmodule.exports自体が処理を終えてしまうからです。

レスポンスらしいレスポンスを設定しないまま処理が終わってしまうので、502エラーを返す、ということです。

対応方法

結論から書くと、以下の2点を直すと良いです。

  1. module.exportsを、Promisereturnするように変更する。
  2. function.jsonにて、http output bindingsのname$returnにする。(portalの場合、応答パラメータ名のところにある「関数の戻り値を使用する」をチェックする)

なおしてみる

なおした後の実装がこちら。

// function.json
{
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    }
  ],
  "disabled": false
}
// index.js

module.exports = function (context, req) {
    // Searchクライアント
    const AzureSearch = require("azure-search");

    // 検索ワードをスペースで切って配列にする
    const words = req.query.word ? req.query.word.split(' ') : [];

    // ページング指定
    const page = req.query.page ? parseInt(req.query.page) : 1;
    const top  = req.query.size ? parseInt(req.query.size) : 10;
    const skip = top * (page - 1);

    // 検索オプション
    const search = words.map(w => `body:${w}`).join(' AND ');
    const filter = 'visible eq 1';
    const searchOptions = {
        queryType:  "full",
        searchMode: "all",
        top:        top,
        skip:       skip,
        search:     search,
        filter:     filter
    };

    // 問い合わせ
    const promise = Promise.resolve(AzureSearch({
        url: process.env.SEARCH_URL, 
        key: process.env.SEARCH_KEY
    }))
    .then(client => client.search('message', searchOptions))
    .then(results => {
        return {
            status:  200,
            headers: {"Content-type": "application/json"},
            body:    results
        };
    })
    .catch(err => {
        return {
            status:  500,
            headers: {"Content-type": "application/json"},
            body:    {"message": `Internal Server Error: ${err}`}
        };
    });

    return promise;
};

function.jsonでは、http output bindingsのname$returnとなっており、functionの戻り値をレスポンスに使う設定となっています。

そしてindex.jsではPromise.resolve(...).then(result => ...).catch(err => ...)の形式でSearchに問い合わせを行った後の処理をハンドリングするよう定義し、promiseそのものをreturnするロジックへと書き換えられました。

本当はドキュメントに書いておいて欲しかった、もしくは・・・

実はPromiseをreturnすることで解決できるという事について、公式ドキュメントには書かれていないようです(ソース。openになっているし、書こうとはしてる模様)。

今回の解決法は、上記issueを辿ってようやく見つけることができたものでした。

この手の手間をなくすためにも、Search Bindingsが欲しいな、と思うのでした。

まとめ

  • Search Bindings欲しいですね。

2018-08-30 追記

node-azure-searchでPromise/thenを使った書き方では、検索結果のヒット件数を取得することができないという問題がありました。

const AzureSearch = require('azure-search');
const word = '5000兆円';
AzureSearch(...)
    .then(client => search({
        queryType:  "full",
        searchMode: "all",
        top:        20,
        skip:       0,
        search:     `message:${word}`,
        filter:     'visible eq 1',
        orderby:    'timestamp desc',
        count:      true // <--- @odata.countをレスポンスに含めるための指定
    }))
    .then(rows => {
        // rowsは検索結果(オブジェクト)が入った配列。
        // ここで検索結果のヒット件数である@odata.countを利用したいができない!!
    })
    .catch(err => { ... });

これはnode-azure-searchのindex.jsを修正することで、取得できるようになります。(ただしインターフェイスを破壊する変更です)

@@ -492,7 +492,7 @@ module.exports = function (options) {
            return new Promise(function (resolve, reject) {
              args.push(function (err, value, data) {
                if (err) reject(err)
-               else resolve(value)
+               else resolve(data) // resolve(value) not contains '@odata.count'
              })
              fn.apply(self, args)
            })

利用する側は以下のようになります。

const AzureSearch = require('azure-search');
const word = '5000兆円';
AzureSearch(...)
    .then(client => search({
        queryType:  "full",
        searchMode: "all",
        top:        20,
        skip:       0,
        search:     `message:${word}`,
        filter:     'visible eq 1',
        orderby:    'timestamp desc',
        count:      true // <--- @odata.countをレスポンスに含めるための指定
    }))
    .then(result => {
        const count = result['@odata.count'];  // 検索結果のヒット件数。
        const rows  = result['value'];         // rowsは検索結果(オブジェクト)が入った配列。 
        ...
    })
    .catch(err => { ... });

破壊的な変更であるため、forkして利用しています。

Hokkaido.pm #14で 予想の話 というか 予想を支えるシステム設計の話 をしてきました

予想の話予想を支えるシステム設計の話

札幌で開催されたHokkaido.pm #14というイベントに参加し、予想の話(というか予想を支えるシステム設計の話)をしてきました。

内容としては「おとなの自由研究」の取り組みの中で作った南関競馬予想システム「うまミる」のシステム構成と予想ロジック、運用などについてを話したのですが、30分は予想以上に短く、あらかじめ40分で発表枠を押さえておけばよかったというのが正直な感想です。

うまミるについてより詳しく聞きたい方は、アルコールチャンスを用意してくださればお話しますので、是非ともよろしくお願いします。

Hokkaido.pmの皆さん、本当に楽しかったです。ありがとうございました。出来たら11月くらいにもう一発くらい開催してくれたらいいのにな、って思いました!

あと当然ながら、初夏の札幌を満喫したのは言うまでもありません。

Gotanda.pm #18 六本木編 でLTしました

Gotanda.pm #18 六本木編で、Core Modulesに寄せてみたら?というタイトルでLTしてきました。

オフィスから歩いていける場所ということもあり、割と早々とLTすることを決めたんですが、勢いは大事ということでLTもどうにか勢いを維持して発表できたんではないかなと自負しております。

「システムプログラミング」というテーマだったようでして、 strace を使ったperlのopenとシステムコールの結びつきについて詳説したトーク(kazeburoさん)や、ネタが被ったと言いながらも、別の切り口によるディープなトーク(skajiさん)、はてなブログSSL化の裏側についてのトーク(papixさん)など、全体的に「掘り下げられた発表」が多かったように感じました。

Mishima.pm #3 に参加してきました

Mishima.pm #3というイベントが6/3(日)に開催されました。

主催の@dokechinさんが三島在住ということで、数年前から不定期的に開催されていまして、私は #0, #1, そして今回の #3 と、開催されるたびにできるだけ行こうと思っているpmの一つです。

今回の参加者は私を含めて4名。当日の様子は#mishimapmtogetterにもまとまっていますので、そちらも併せてご覧いただけると雰囲気が伝わりやすいのではないでしょうか。

「Why people says “Perl is guilty”?」というタイトルで発表

こちらに発表スライドのMarkdownを置いてありますので、そのままご覧いただくか、reveal.jsで任意のテーマを当ててご覧ください。

この発表で言いたかったこと

かなりポエムっぽいことなのですが・・・

様々な言葉でPerlを貶める人はいるし、私も彼らの言っていることにも一定の理解を示します。確かに一部は事実でしょう。

しかしそれらは大抵、他の言語で鳴らしてきた人が、大して知りもしないPerlについて語っているだけではありませんか?少なくとも私にはそう見えます(私がPerlのすごい人かと言われると、そんなこともないんですけど)し、悲観的すぎるだろうと思います。

そんなことを気にするより、低い理解度でも自分の思った処理を書くことができることの方が大事だと思いますし、Perlにはそのためのツールセットが揃っているんだ、と言いたい。

実は覚えることがかなり少なくて良いので(例えば変数記号については$と@と%を理解できればひとまずOK)、まず書いてみて、足りないところの知識を補いながら少しづつ上達するのに向いている言語なんです。

斜に構えて100%理解した風に振る舞うより、まず手を動かして試すところからPerlを触ってみると、意外といいものだと思えますよ。

・・・スライドからは読み取れないでしょうけど、こんな感じのことを話しました。

コンテキストの観点でPerlを理解する

私の発表の前に@karupaneruraさんの発表内容が、Perlのコンテキストに関する話題に触れていました。

例えば$age = 100というデータについて、

  • $age . "歳のおじいさん" という文字列的な扱い
  • $age + 1 という数値的な扱い

の両方を同じ変数に対して行えることは、コンテキストを意識することで、一見複雑に思える挙動を理解できるという感じの話でした。

似たようなことをやっている言語はLispやFORTHなどが挙げられます。

この考え方のメリット・デメリットについて書くと長くなるので別の機会に回しますが、Perlについての理解を深める視点として、非常に慧眼だなと感じました。

三島を満喫する

うな丼

三島で八王子

竹倉温泉の待合室

新幹線コンコースから望む富嶽

webアプリを提供する上でやめた7つのもの

よもやま話など

ytnobodyです。実に3か月ぶりの更新です。ご無沙汰しております。

最近はPerlやサーバサイド、クラウドなどについて、個人的にこれといって真新しいとかすごい、と感じたものがあまりなく、そのため更新が滞っておりました。

また、私の主業務におけるiOSアプリ開発の割合が非常に高くなっており、そちら側のキャッチアップにエネルギーを注ぎまくっている状況でして、これがまた楽しくて楽しくて。

・・・というような事情で、なかなかこちらにはアウトプットできていませんでした。

今日はそんな状況を顧みて、「じゃあ自分がいまどういう哲学に基づいて設計・開発などを行っているかをひけらかしてみたらどうだろうか?」と思い立ち、キーボードをたたいている次第です。

もはや当たり前、と思われる方もおられるかもしれません。まぁしかし、個人のブログですし、お目こぼしくださると幸甚です。

本題

さて、webアプリを公開するにあたって、いくつか「最低限これは必要だよね」というものがあると思います。私の場合、7年ほど前であれば以下のものを挙げたことでしょう。

  • ドメイン名
  • ネームサーバ(とりあえず外部のやつでもよい)
  • webサーバ(もしくはVM)
  • ネットワークおよび構成機器(ルータだとかスイッチだとか)
  • OS(UbuntuとかCentOSとか)
  • httpd(ApacheとかNginxとか)
  • プログラミング言語の動作環境(perlだとかphpだとかrubyだとか)
  • デプロイ手段(capistranoとかansibleとかftpとかscpとか)

ここでは「最低限」なので、DBは割愛しました。しかし、こうして見てみると結構多いですね。

では現在はどうかというと、以下のものを挙げます。

  • ドメイン名
  • DNS as a Service(Amazon Route53だとかGoogle Cloud DNSだとかAzure DNSだとか)
  • Platform as a Service(Amazon Elastic BeanstalkだとかGoogle Apps EngineだとかAzure Web Appsだとか)
  • git

だいぶ少なくなりました。減ったものについて着眼していきます。

ネームサーバ

DNS as a Serviceに取って代わられました。

DNS as a Serviceを利用するにも相変わらずネームサーバの知識は必要なのですが、物理的にサーバを用意したりする必要がなく、クラウドプラットフォームへのログインが可能な状況であれば、いつでもリソースレコードの修正や追加が可能となりました。

webサーバ

Platform as a Serviceに取って代わられました。

これにより、VMや物理サーバの運用をする必要がなくなりました。概念的にはサーバがなくなったことになります。

ネットワークおよび構成機器

概念的にサーバがなくなってしまったことによって、ネットワークとその構成機器もまた概念的に不要となりました。

従来これらが賄っていたことはPlatform as a Serviceの一機能として提供されているか、あるいはLoad Balancer as aa Serviceによって代替できるため、必要があればLoad Balancer as a Serviceを追加する程度です。

OS

概念的にサーバがなくなっていますので、OSも概念的に不要となりました。

これはPlatform as a ServiceによってOSが自動的に提供され、かつ更新が行われるようになったためです。

httpd

これもPlatform as a Serviceによって不要となりました。

ApacheやNginxなどの複雑なconfigを書く必要がなくなり、PaaS側に組み込まれた機能によって代替できるためです。

プログラミング言語の動作環境

Platform as a Serviceに組み込まれているもので十分なので、自分で用意することはやめました。

最近はPHP7に寄せておりますが、JavaやC#、pythonなどもよさそうだと感じます。

デプロイ手段

デプロイ手段を自前で用意するのもやめました。

Platform as a Serviceがgitに対応しており、tagを切ることでデプロイが行われるように設定しました。

やめたことで得られたもの

OSやその下位レイヤであるVM/サーバの管理から解放された

PaaSを使う主目的の一つとしてあげられるメリットですが、その大きさは想定以上でした。

思えば、私が過去にやってきた業務の半分近くが、VM/サーバの管理やOSの管理だったという時期があったくらいですから、従来の半分強のエネルギーで業務が回るようになったと言えます。

ネットワークの管理から解放された

OSやVM/サーバもそうなんですけど、一からネットワークを構築して運用することは、それだけで一つの仕事として成り立つくらいに高度かつエネルギーや時間を使う行為です。

また、ネットワークで問題が発生した時には、そのトラブルシュートに必要な知識は専門性が高く、エンジニアのジョブセキュリティが高まってしまいます。そのため、ひとたび問題が発生すると、解決までは帰宅できないという事態が発生します。

OS、VM、ネットワークの管理から解放されるということはつまり、「エンジニアでありながらも、いつも定時で帰宅しプライベートを満喫できる」という状況が、確固たるものになるということではないでしょうか。

httpdの設定から解放された

httpdの設定は一箇所間違うと、サイト全体に致命的な影響を及ぼすことがあります。ネットワークの問題にも性質が似ているのですが、基本的には「解決しないと帰れない」を生み出す可能性を内包しております。

また、OS同様にセキュリティ上の問題が発見された場合、その対応をするのはhttpdを管理している人間が担当することになります。

これらの管理コストをPaaSに代替させることで、利用料金という形であらかじめ解決しておくことができるのは、不確定要素を予定調和化する上で良い判断だったと考えます。

プログラム動作環境・デプロイ手段の整備から解放された

プログラム動作環境の構築やデプロイをAnsibleなどの構成管理ツールで対応することは、もう3年以上前には行われていたことですが、playbookの作成と管理に心理的コストがかかることをずっと気にしていました。

ところがPaaSを使うことで、プログラム動作環境は毎回お仕着せのものが降って来ますし、デプロイについてはgitレポジトリの特定ブランチにpushするか、tagを切ることで実施されるようになりました。

結果、playbookの管理にかかる心理コストがなくなり、「デプロイをするぞ」という意識から、「pushするぞ」「tag切るぞ」という意識へと移ろい、「デプロイをする」という作業自体がなくなりました。

負荷をあまり気にしなくなった

完全に気にしなくなる状況には至っていないのですが、それでもVMを自分で管理するのに比べて、オートスケール機能が設定されたPaaSの場合は負荷に対する心理的コストが圧倒的に低いと感じます。

手が空いた分iOSアプリの開発ができるようになった

もともと私はサーバサイドアプリ(要はwebアプリ)がメインで、インフラを少々嗜む程度のスキルセットでしたが、新しいスキルを習得する余力ができたのは、明らかに従来の業務遂行に必要な時間やエネルギーが半減したからだと考えます。

得られたものから考える

ここまで書いてきたことは、唯一の判断基準「手間・管理の削減」を突き詰めた結果です。

httpdの項目でも書いた「不確定要素を予定調和化する」ということは、手間・管理を削減するうえでキーとなる考え方ではないでしょうか。

歴史を見ると、「速く快適に移動する方法」として馬や馬車、人力車などが広く利用されていた時代がありました。ところが時代が進むにつれ、馬も馬車も人力車も利用シーンが激減するのですが、その一番の理由は「自動車の普及」でした。

「webアプリを公開する方法」という本質だけを見た時に、歴史や得られたものから考えると、何がより進化したものなのかを思わずにはいられないものです。そして、進化したものはいつも「斬新」で「ミニマリズム」であると、私は考えます。

レビュー RAZER Blade Stealth 2017

RAZER Blade Stalth 2017 を購入してから1か月ほどが経過しましたので、レビューしてみます。

WELCOME TO THE CULT OF RAZER

閉じた状態

閉じた状態

※もう1か月使っているので、多少の汚れはご勘弁を。サイズ感が分かるようにコミックを隣に置いてみました。

まず、Windows PCのわりにシンプルで洗練されたデザインが目を引くところでしょう。アルミ削り出しの筐体はブラック(写真)とガンメタリックの2種類から選択でき、背面にはRazerのシンボルがしっかりとあしらわれています。

真正面から

真正面から

閉じた状態で厚さは15mmないくらいとかなり薄く、そして1.25kgと、13.3インチPCのわりには軽い部類に入ります。

左側

左側

左から Thunderbolt™3(USB-typeC/電源供給兼)、USB3.0(SuperSpeed)、ヘッドフォン/マイク複合ジャック。USB-typeAの口が欲しい!というケースにもちゃんと対応してます。

右側

右側

こちらは左からUSB3.0(これもSuperSpeed)、HDMI2.0。フルサイズのHDMIポートがあるのは、セミナーなどで登壇する際に非常に重宝します。

裏側

裏側

ラバー製の足がちょっと汚れてますが、これのおかげでデスクの上で滑ることなく利用可能となっています。ゲーマーにとっては非常に重要なパーツなのでは?

そして見えているファンは吸気ファンとなっていて、排気はスピンドル側にある排気口から出ていくことになります。かなり排熱に気を使った構造です。

開けてみる

開けてみる

ほとんどUSキー配列のMacBook Proと同じようなレイアウトですね。キーボードはMacBookに慣れている方ならばあまり違和感を覚えないような打鍵感となっていますが、幾分フィードバックが強めな気がします。

Macと違うのは、Touchバーがあるはずの場所にファンクションキーが並んでいるところでしょうか。電源ボタンはキーボード上部中央にわかりやすく配置されています。

トラックパッドはほぼMacBook Proと同様の操作感となっており、3本指スワイプの挙動もWindows10からかなりMacOSに寄せてきています。

キーボード両脇についているスピーカーについては、ぜいたくを言わなければ十分納得できるクオリティですが、より本格的なオーディオを楽しみたい場合はRazer TIAMAT 7.1 v2あたりで「Razer寄せ」してみるのがよさそうです。

ディスプレイはQHD(3200×1800)のIGZOタッチディスプレイとなっており、ゲーミングPCを標榜するだけあってハイクオリティです。

七色に輝くRazer Chroma™

七色に輝くRazer Chroma™

キートップはRazer Chroma™と呼ばれるライトアップ機能によって常に七色に光り輝いており(設定変更でカスタム可能)、暗所でもキータイプに支障が出ないようになっています。これこそRazerならではの粋な仕様ですね。

背面

背面のRazerシンボルもグリーンに光ります。

スペックとバッテリー

私が利用しているモデルは256GB SSDのものとなります。CPUはIntel Core i7 7500U(第7世代/デュアルコア)、メモリは16GBを搭載。ちょうどMacBook Proの13インチ版に似た性能ですが、メモリだけ倍積んでいる感じになります。

バッテリーは9時間稼働可能ということになっていますけど、実際のところ6時間程度の稼働という感じです。利用の仕方によって多少の前後はありそうですけど、目安としてそのくらいです。

グラフィック性能

内臓しているグラフィックチップは Intel® UHD Graphics 620 となっており、正直単体ではそんなに期待できるものではなかったのですが、Minecraftをプレイするくらいであれば全然問題ありませんでした。

なお、Thunderbolt™3でRazer Core v2を接続することで、グラフィックボードを外付けできるというキワモノな仕組みが存在します。

そのRazer Core v2ですが、公式サイトによると「 NVIDIA® GeForce® GTX 10 シリーズおよび AMD XConnect™ 対応 Radeon™ RX ボードを含む最新グラフィックスチップセットに対応。」とのことで、MacBookっぽいUltraBookがゲーミングシステムに早変わりするというわけです。

総評

筐体に指紋が付きやすいのが弱点ですが、アプリケーション開発にも耐える硬派なスペック、シンプルなデザイン、美しく見やすいディスプレイと、Web系エンジニアにとって決して悪くない選択であると感じました。

UnixではないというWindowsならではの厳しさがあるかもしれませんが、Unixを手元で触らないエンジニアリングスタイルを確立してしまえば、何ら困ることはありません。Unix/Linuxはクラウドにお任せして、手元ではWindowsを使うというやり方は案外楽なものですよ。ぜひVSCodeとChocolateyをお忘れなく。

え、iOSアプリの開発?それはMacを使ってくださいw

書評 Azureテクノロジ入門2018

遅ればせながら、新年おめでとうございます。

さて、昨年末にAzureの風雲児であるところの@myfinder氏より献本いただきました。改めてお礼申し上げます。ありがとうございます。

本書について

2016年11月に出版されたAzureテクノロジ入門2016の改訂版となります。

もともとAzure関連の技術書は数少ないのですが、本書はその中でも入門者・初学者向けの位置付けとして発売されています。索引まで含めて233ページとなっており、定価は2500円と、技術書としてはかなり安価です。

著者陣はほぼ日本マイクロソフトの現任エバンジェリストやアーキテクトなどで占められており、マイクロソフトがいかにAzureというプロダクトを重要視しているかがわかります。

本書の目次

  1. Azureの基本と全体像
  2. AzureのインフラとIaaS ~ 仮想マシン、ストレージ、ネットワーク
  3. データベースと分析サービス
  4. 開発者のためのPaaS ~ Azure App ServicesとAzure Functions
  5. アイデンティティ管理と認証・認可
  6. 地上に広がるハイブリッドクラウド ~ Azure Stack

書評

表紙に「Azureを知るための最初の1冊!」「さらに進化したAzure全体像がよくわかる!」と書かれているのですが、まさにその通りの内容です。

そこそこAzureを使っている立場からすると、2章と5章は順序が逆でもいいかもしれないな、と感じました。AzureにおいてはそのくらいにID管理(Azure Active Directory 通称Azure AD, AAD)が肝となりますし、IaaSについてはAzureのPaaS/SaaSについて理解してからフォーカスするくらいでも遅くないと思います。

とはいえ、各章は初学者にもわかりやすいよう図解が多く用いられており、概念を理解する上で非常に心強い一助となることでしょう。すべて通読する必要はなく、手始めに利用する予定のあるサービスに応じて、必要な個所だけピックアップして読むのがおすすめです。ある程度ユースケースをカバーできたら、今度は通読してAzureの可能性をもっと広く学習することができます。

また、ある程度熟練した向きにとっても、次に利用する予定のAzureサービスについて手早く把握しておきたい場合に、辞書のように利用できることでしょう。というのも、本書では各章の要所要所にオンラインドキュメントのURLが記載されており、細かい部分についてはそちらで補完するような構成となっているためです。

あらゆることを正確に記憶しておける人間は稀です。本書は、Azureに関する知の高速道路を読者に提供してくれることでしょう。そして忘れかけたAzureの知見を改めて引き出す際にも十分に価値を発揮してくれると思います。