CTOの役割と組織における技術的ポートフォリオの組み方

はてなCTOのid:stanakaです。 はてなアドベントカレンダー2014も最終日となりました。 今年のアドベントカレンダーは、スマートフォンアプリ開発からシステム系論文の話まで幅広いテーマが集りました。 読んでいて優秀なエンジニアがいるなぁ、としみじみ思います。

ちなみにアドベントカレンダーは25日までじゃないのか、という話がありそうですが、本来は24日までだそうです*1

CTOとは何か論

最終日の今日は「CTOの役割と組織における技術的ポートフォリオの組み方」について考えているところを書いてみます。

最近、なぜかCTO論が盛んで、あちこちでよく耳にするようになってきています。 rebuild.fmでのnaoyaさんのマネージメント話や、WEB+DB Pressの舘野さんの連載などでもCTOやエンジニアのキャリアについての話が盛り上がっています。 つい先日でたWEB+DB Pressの藤本さんとnaoyaさんによるCTO特集も読み応えがありました。

私も先日、IVSの併設イベントで「CTOに求められるもの」という講演をさせていただきました。 100人のCTOがいれば、100の役割があると思いますので、この講演での私の事例もその一つと考えていただければ、と思います。

またCTOの役割の中でも開発マネジメントの側面についてはnaoyaさんの開発組織のマネジメントが珠玉ですので、こちらを読むのが早いです。

技術的ポートフォリオと組織能力

今日の話はCTOの役割の一つに数えられるであろう「技術的ポートフォリオの組み方」についてです。

はてなは主にWebサービスを提供している会社ですが、そこで必要とされる技術は、ざっくり以下のようなものです。

  • サーバーサイドアプリケーション関連(主にPerl / Scala)
  • フロントサイド関連 (主にJavaScript)
  • スマートフォンアプリケーション(iOS / Android)
  • インフラ (Linux / DBMS / 各種DataStore / AWSなどのIaaS / 各種ネットワーク)
  • 開発環境関連( CI / デプロイ / 各種開発周辺ツール )
  • 自然言語処理、機械学習

はてなのエンジニアはおよそ30名弱なのですが、その人数でこの領域全体をカバーしています。 また、それぞれの領域の中でも求められる具体的な技術については、時期とともに変化があります。

例えば、サーバーサイドアプリケーション関連の技術については長らくPerlを主要な言語とし、Perlで利用するライブラリやフレームワークを新しくしてきたのですが、最近、ここにScalaが加わりました。 (ちなみにPerlを辞めたわけではなく、引き続きはこれからも適材適所で使い続けます。) またインフラ周りについても、数年前までは自作PCを作って使っていたのがベンダーからサーバーを購入するようになり、最近ではAWSのようなIaaSを使うハイブリッド環境に変化してきています。 (インフラレイヤーのより上位への変化は決して小さくないインパクトです)

このような技術的な変化 / 進化は常に起きており、その中で自社のエンジニアにそれぞれどのような技術を身につけてもらい、また将来を見据えてどのようなエンジニアを採用していくか、というのはCTOの大事な役割の一つでしょう。

いま時のWebサービスやスマートフォンアプリを開発している会社では、Webサービス、スマートフォンアプリをさっと作り、すばやくリリースすることが重要視されており、多くの会社は徐々にそれを身につけつつあると思います。

その背景として、数年前からリーンスタートアップ的手法や各種のモダンな開発プロセスの普及があると考えています。 プロダクトを最初はミニマムに作り、実際のニーズに応じて作り込んでいくという方法論はとても強力で、 このような手法でサービスを機動的に作っていくのは確かにとても大事です。

一方で、このような開発手法を支える技術的なノウハウは、それなりの時間を掛けて組織内に蓄積していく必要があります。

GMOペパボの技術責任者の栗林さんも組織能力を圧倒的に成長させることにおいて、技術に関する組織能力を高めることの重要性を指摘しています。

またもう一つ、PFIの岡野原さんによる技術と時機において、成功した技術と失敗した技術の時機についての考察があります。 中でも「(4)一方で準備は数年前からはじめていなければならない」という一文が非常に心に残りました。

高速道路とその先へ

実現しようとしているサービスの優位性が技術的なものかそうでないかで、この先の考え方は大きく変わってくると思います。 ただCTOとしては、はてなが提供するサービスには(もちろん他の様々な要素も極めて重要ですが)技術的な点での優位性を持たせることを常に目指したいと考えています。

最近ではスマートフォンアプリ開発を一から勉強して短期間でリリースした、という例も目にしたりします。 もちろんそれでヒットすることもあるでしょうし、それを否定する気はまったくありません。

ただ技術的な観点では、それは先人がちょっとずつ技術的環境を整えてできあがった高速道路を走っている、ということだと思います。 つまり2014年末の時点でスマートフォンアプリで技術的優位性を築こうと思ったら、高速道路がまだ敷かれていない領域に乗り込む必要があります。 例えば、画像認識技術だったり、ユーザーのコンテキストを認識する技術だったり、よりスピード感のある開発プロセスの導入だったりするでしょう。

もちろん、この類のことはスマートフォンアプリに限らず、ウェブサービス一般について言えることだと思います。

高速道路がない領域に乗り込むために、僕は次の2点を意識しています。

  • まず、高速道路がある領域はしっかりと高速道路を利用する
  • 高速道路の先を開拓するために、技術的な目線を高める

前者ははてなインターン研修用教科書を公開するような取り組みや、開発フローの改善に表れているように、できるだけスムーズに開発できるように心掛けています。

また後者についても様々な取り組みを行っているのですが、最近は世界の最先端の技術に触れる機会を増やすためにも論文輪読会を定期開催しています。 (最近、僕はあまり参加できてないですが、ちゃんと参加したいです。) 詳しくはアドベントカレンダーの昨日のエントリ「インフラエンジニア向けシステム系論文」にも書かれています。 ちなみにですが、はてなは比較的Ph.D持ちのエンジニアの比率が高く、全体の2割ほどになっています。

このような取り組みを続けて、自社の組織内に様々な技術へのアンテナを持ち、かつ、それらが血肉となるようにしていきたいと考えています。 その中から、しっかりとした技術的な価値と優位性のあるプロダクトを出していけるように研鑽していきたいものです。

おわりに

もちろん、CTOであるところの僕も今後もしっかりと技術にコミットしていきたいと思ってます。(もちろん経営にも。) また日本ではCTOの役割としてマネジメントの側面が強調されることが多いのですが、僕としてはもうすこしコードに関わりたいと考えていて、miyagawaさんのTweetで言及されているエントリに共感しています。

#define CTO

ということで、来年2015年は今年よりコードを書ける年にする、というのを抱負にしたいと考えています。

では、皆さま、よいお年を!!

gulpで依存関係を考慮した自動コンパイル

こんにちは、シニアアプリケーションエンジニアのid:taraoです。はてなエンジニアアドベントカレンダー2014の21日目として、依存先のファイルが更新されたらコンパイルしなおす処理をgulpで実現する方法について、とくにLessを例にとって紹介します。

はてなでは、JavaScriptやCSSなどの静的ファイルをTypeScriptやLessなどからコンパイルして生成することが増えています。CSS(Less)は主にデザイナが書くため、コンパイル手順はできる限り簡単にする必要があります。多くのチームでは、サーバアプリケーションをローカル環境で実行している最中はファイルの変更に応じて自動的にコンパイルしなおすようになっています。

ファイルの更新監視からコンパイルまでの処理にはGruntなどを使ってきましたが、コンパイル対象のファイルに依存関係がある場合、依存先のファイルの変更に応じて依存元のファイルのみをコンパイルしなおすのが難しいため、諦めて1つのファイルが更新されたら全ファイルをコンパイルしなおすようにするか、依存解析機能つきのコンパイルサーバを独自に実装してGruntとは別に使用していました。

最近になって一部のチームでgulpを使うようになり、依存解析の結果からコンパイルしなおすべきファイルを絞るのが比較的容易に実現できることがわかったので、Lessの依存指定(@import)を解析するgulpプラグインを実装したというのが、今回のお話です。

gulpの基本的なしくみ

まずはstatic/less以下のLessファイルをコンパイルして、static/css以下の同名ファイルとして保存するgulpfile.jsを書いてみましょう。@importするファイルはCSSを生成する必要がないので、そのようなファイルはファイル名の先頭に_をつけてコンパイル対象から除くこととしましょう。

'use strict';

var LESS_DIR = 'static/less';
var CSS_DIR = 'static/css';

var path = require('path');

var gulp = require('gulp');
var logger = require('gulp-logger');
var filter = require('gulp-filter');
var lessc = require('gulp-less');

gulp.task('less', function() {
    gulp.src(path.join(LESS_DIR, '**/*.less'), { base: LESS_DIR })
        .pipe(filter([ '*', '!**/_*.less' ]))
        .pipe(lessc())
        .pipe(gulp.dest(CSS_DIR))
        .pipe(logger({ beforeEach: '[less] wrote: ' }));
});

これでgulp lessでLessファイルを一通りコンパイルできるようになりました。

> gulp less
[hh:mm:ss] Using gulpfile /path/to/gulpfile.js
[hh:mm:ss] Starting 'less'...
[hh:mm:ss] Finished 'less' after xxx ms
[hh:mm:00] [less] wrote: static/css/test.css

gulp.src()はまず処理対象のファイルを列挙し、それぞれのファイルに対して.pipe()された処理を順に実行していきます。列挙されたファイルには、どこからが相対パスかという情報(baseで指定)も含まれていて、最終的な処理結果はgulp.dest()されたディレクトリ以下の対応する相対パスに書き出されます。

gulp.src().pipe()の関係はArrayArray.prototype.map()の関係に似ていますが、もう少し柔軟性が高く、.pipe()された処理が新たなファイルを列挙したり、列挙されていたファイルを取り除くこともあります(つまりどちらかというとArray.prototype.map()よりはArray.prototype.reduce()だとおもった方がよいですね)。上の例の.pipe(filter(...))はちょうどArray.prototype.filter()のように、具体的な処理はせずに受け取ったファイルのうち条件に合うものだけを後段の.pipe()に渡します。

ファイルの更新監視

ファイルの更新を監視してタスクを実行するにはgulp.watch()が使えますが、上で説明したArrayとその操作関数というメタファーに合致しているgulp-watchを使ってみましょう。

'use strict';

var LESS_DIR = 'static/less';
var CSS_DIR = 'static/css';

var path = require('path');

var gulp = require('gulp');
var logger = require('gulp-logger');
var watch = require('gulp-watch');
var filter = require('gulp-filter');
var lessc = require('gulp-less');

gulp.task('less', function() {
    gulp.src(path.join(LESS_DIR, '**/*.less'), { base: LESS_DIR })
        .pipe(watch(path.join(LESS_DIR, '**/*.less')))
        .pipe(filter([ '*', '!**/_*.less' ]))
        .pipe(lessc())
        .pipe(gulp.dest(CSS_DIR))
        .pipe(logger({ beforeEach: '[less] wrote: ' }));
});

.pipe(watch())は前段から渡されてきたファイルに加えて、将来に渡って更新ファイルが要素として含まれる無限リストを後段に渡す、と理解することができます。実行直後は、gulp.src()で指定されたファイルが後段に渡されて、前述の通りコンパイルされます。その後、watch()に指定したファイルに変更があると、そのファイルをリストの続きとして後段に渡していきます。

依存解析

gulp.src()で指定したファイル以外のものをwatch()が後段に渡すように、前段から渡されたファイルに依存しているファイルを後段に渡すようにすることで、依存解析のみを実行する部品を組み込む形で目的を達成できそうです。まずはこの部品の仕様をもう少し細かく決めてみましょう。

  • 前段から渡されたファイルは依存解析の対象
    • @import 先のファイルを覚える
  • 前段から渡されたファイルに依存しているファイル後段に渡す

この仕様では、依存先ファイルも依存元ファイルも、どちらも最低一度は前段から渡されてくる必要があります*1。依存先のファイルがライブラリコードのようなものなら後段でコンパイルする必要はないはずですが、いったんぜんぶ渡しておいて不要なものはfilter()で取り除くというモデルです。以下のlessDependents()のように使うイメージです。

'use strict';

var LESS_DIR = 'static/less';
var CSS_DIR = 'static/css';

var path = require('path');

var gulp = require('gulp');
var logger = require('gulp-logger');
var watch = require('gulp-watch');
var lessDependents = require('gulp-less-dependents');
var filter = require('gulp-filter');
var lessc = require('gulp-less');

gulp.task('less', function() {
    gulp.src(path.join(LESS_DIR, '**/*.less'), { base: LESS_DIR })
        .pipe(watch(path.join(LESS_DIR, '**/*.less')))
        .pipe(lessDependents())
        .pipe(filter([ '*', '!**/_*.less' ]))
        .pipe(lessc())
        .pipe(gulp.dest(CSS_DIR))
        .pipe(logger({ beforeEach: '[less] wrote: ' }));
});

プラグインの実装

依存解析部分をgulpのプラグインにしてみます。gulpプラグインはストリームオブジェクトを返す関数として実装します。through2を使うとストリームオブジェクトを簡単に定義できます。実際の依存解析部分はLessDependencyというクラスでやることにして、まずはプラグインの外形を定義します。

'use strict';

var _ = require('lodash');
var fs = require('fs');
var through = require('through2');
var LessDependency = require('./lib/less-dependency');

module.exports = function() {
    var dep = new LessDependency();

    var stream = through.obj(function(file, enc, callback) {
        dep.parseImports(file);
        _.uniq(dep.accumulateDependentsOn(file.path)).forEach(function(fname) {
            var f = file.clone();
            f.path = fname;
            f.contents = fs.readFileSync(fname);
            this.push(f);
        }.bind(this));
        return callback();
    });

    return stream;
};

through.obj()に渡しているfunctionの中に、1つのファイルが前段から渡ってきたときにやるべき処理を書けばよいようになっています。

まず、dep.parseImports(file)fileが依存しているファイルについての情報を蓄積します。これはfileが依存元ファイルだった場合の処理で、集めた情報は通常は次回以降の実行のときに使われます。

続いて、dep.accumulateDependentsOn(file.path)file依存しているファイルを列挙します(つまりfileが依存先ファイルだった場合の処理です)。列挙されるファイルの中にはfile自身も含まれるものとします。

こうして列挙されたファイルをthis.push()していくと、それらが後段に渡されるファイルということになります。this.push()に渡しているffile.clone()して作っているのは、相対パスの情報を維持するためです。

あとはLessDependencyを定義するだけですが、このクラスの実装にはLessコンパイラ本体のクラスを利用するのがよいでしょう。そうすれば依存解析結果がコンパイラと乖離せずに済みます。

'use strict';

var _ = require('lodash');
var path = require('path');
var less = require('less');

var LessDependency = function() {
    this.dependents = {};
};
LessDependency.prototype.depend = function(dependent, on) {
    var list = _.uniq((this.dependents[on] || []).concat([dependent]));
    this.dependents[on] = list;
};
LessDependency.prototype.parseImports = function(input) {
    /* input の @import を、lessを使って集める */.forEach(function(file) {
        this.depend(
            input.path,
            path.resolve(path.dirname(input.path), file)
        );
    }.bind(this));
};
LessDependency.prototype.accumulateDependentsOn = function(path) {
    if (this.dependents[path]) {
        return this.dependents[path].reduce(function(r, f) {
            return r.concat(this.accumulateDependentsOn(f));
        }.bind(this), [path]);
    } else {
        return [path];
    }
};

module.exports = LessDependency;

parseImports()はLessコンパイラを使って依存先ファイルの情報を集めます。実際の依存関係は、依存先から依存元への対応関係としてthis.dependentsに保存されます(depend())。accumulateDependentsOn()は、this.dependentsを使ってpathに依存しているファイルを再帰的に列挙します。

このようにしてできたLessの依存解析プラグインはgulp-less-dependentsとして公開しているので、どうぞご利用ください。

おわりに

Lessのコンパイルを例に、gulpで依存関係をていねいに扱うプラグインを作成する方法を紹介しました。実はgulpのプラグインを作るのもnpmのモジュールを作るのも初めてでしたが、gulpの概念がおおよそわかればスマートに実装できますね!

はてなでは、開発ツールを積極的に改善していけるエンジニアも募集しています。

*1:他にオプションで解析対象を渡すという方法もありえます

iOS開発で利用しているオープンソースのライブラリやサービスを紹介

こんにちは。アプリケーションエンジニアのid:sakaharaです。 この記事ははてなエンジニアアドベントカレンダー2014の20日目です。 昨日はid:wtatsuru によるはてなにおけるサーバリソース可視化とMackerelでした。 今日ははてなのiOS開発で利用しているオープンソースのライブラリやサービスも含め、開発をよりスマートにしてくれる様々なものを紹介したいと思います。

パッケージ管理

CocoaPods (MIT License)

まずライブラリ管理をするための管理ツールとして欠かせません。 最近ではSwiftに対応したCarthageも登場していますが、CocoaPodsのSwift対応版の開発も進んでおり そのまま使い続けるのもよいと思います。

ライブラリ

AFNetworking (MIT License)

改めて紹介する必要はないほどの定番のHTTPネットワーク通信用ライブラリです。 Swiftで実装するならAlamofireもよいと思います。 両ライブラリ共にiOS開発で著名なMattt Thompsonさんが開発されています。

AFNetworking

Mantle (MIT License)

JSONをObjective-Cのオブジェクトに変換してくれるライブラリです。 オブジェクトをJSONにして出力することもできます。 またNSManagedObjectへの変換にも対応しているので、Core Dataを使う際にも威力を発揮します。

SDWebImage (MIT License)

こちらも定番の画像を非同期でダウンロードしてくれるライブラリです。 特に何もしなくてもキャッシュをうまくコントロールしてくれる点や、アニメーションGIFにも対応しているなど便利な部分が多いです。 AFNetworkingにもUIImageView+AFNetworkingというライブラリがあり、非同期で画像をUIImageViewに表示する機能はありますが、 このSDWebImageをお勧めします。

MBProgressHUD (MIT License)、SVProgressHUD (Creative Commons BY 3.0)

ローディング中のステータスを表示してくれるライブラリです。 細かい調整をしたいならMBProgressHUD、単純にローディング中を表示したいだけならSVProgressHUDがお勧めです。

MCSwipeTableViewCell (MIT License)、MGSwipeTableCell (MIT License)

どちらもUITableViewCellにスワイプメニューを表示する機能を簡単に実装できます。 MCSwipeTableViewCell

AAMFeedback (New BSD License)、CTFeedback (MIT License)

ユーザーからのフィードバックをメールで送信する際にアプリ名やバージョンなど付加情報を設定できるライブラリです。 簡易的にメールでユーザーからフィードバックを受けたい場合に実装の手間が省けて便利です。

CTFeedback

HMSegmentedControl (MIT License)

UISegmentedControlをタブボタン風にしたUIControlです。 アニメーションや画像、フォントの設定、マージンの調整など細かい部分まで対応しており、タブボタン風のメニューを簡単に実装できます。

HMSegmentedControl

AMScrollingNavbar (MIT License)

SafariようにスクロールするとUINavigationBarを非表示にしてくれるライブラリです。 UITableViewやUIScrollViewに数行追加するだけで実現できます。

AMScrollingNavbar

BBBadgeBarButtonItem (MIT License)

UIBarButtonItemにバッジを表示できるようにするライブラリです。 バッジのフォント、カラー、位置など細かい調整も可能です。

BBBadgeBarButtonItem

RMUniversalAlert (MIT License)

UIAlertView、UIActionSheet、UIActionControllerをBlocksで記述できます。 UIAlertViewとUIActionContrllerの切り替えはOSのバージョンによって自動で行ってくれます。

OHHTTPStubs (MIT License)

HTTP通信を内部でフックしてスタブ用のレスポンスを返してくれるライブラリです。 通信周りのテストでは欠かせない存在です。

サービス

Crashlytics

クラッシュレポートの解析サービスです。 Twitterに買収されたのでご存知の方も多いかもしれません。 自動でdSYMを送ってくれるので別途アップロードする必要がなく運用が楽だったりします。 はてなでもクラッシュの解析には必須の存在です。

TestFlight

こちらはAppleが買収したことでAppleのデベロッパーツールの一部となりましたが、元々のサービスも継続中です。 アプリリリース前のベータテスト用などにユーザーにアプリを配布することができるサービスです。 Apple買収後も通常通り利用できます。

まとめ

いかがだったでしょうか? 今回は主にUI周りのライブラリを中心に紹介させていただきました。 まだまだ他にもたくさんの便利なライブラリはありますが、今日の紹介はここまでとさせていただきます。 こういったライブラリは利用するだけでなく、ソースコードを読むことで理解を深めていくとより良い開発につながっていくはずです。 更に読むだけではなく是非自分でライブラリを作って公開してみることにチャンレジしてみてはいかがでしょうか?

はてなでは、一緒にサービスを開発する仲間を募集しています。

明日はid:taraoさんです。よろしくお願いします!

Swiftでenumとジェネリクスを活用したかっこいいAPIクライアントを書く

この記事ははてなエンジニアアドベントカレンダー2014の16日目です。昨日はid:nobuokaによる「【Retrofit を読む】 利用者が定義したインターフェイスに実装を提供する Java ライブラリの作り方 【リフクレション】」でした。


こんにちは。はてなアプリケーションエンジニアのid:cockscombです。

Webと連携するスマートフォンアプリを開発するとき、Web APIを抽象化したAPIクライアントを作ることがよくあります。これはWeb APIのエンドポイントとメソッドを紐付け、パラメータに名前をつけて、返ってくるJSONのレスポンスを何らかのクラスに当てはめ型付けする、といったようなものになります。

Swiftのモダンな言語機能を利用して、このAPIクライアントを書きましたので、以下に詳解します。例としてGitHubのStatus APIを取り上げています。

またネットワーク通信部分にAFNetworking、JSONからモデルクラスへの対応付けにMantleを使いました。

enumでエンドポイントを列挙する

enum Endpoint {
    case Status
    case LastMessage
    case Messages

    func request<T: MTLModel where T: MTLJSONSerializing>(manager: AFHTTPSessionManager, parameters: [String: String]?, handler:(response: Response<T>) -> Void) -> NSURLSessionDataTask {
        let success: ((NSURLSessionDataTask!, AnyObject!) -> Void) = {
            handler(response: Response<T>.parse($1))
        }
        let failure: ((NSURLSessionDataTask!, NSError!) -> Void) = {
            handler(response: Response.Error($1))
        }

        switch (self) {
        case .Status:
            return manager.GET("/api/status.json", parameters: parameters, success: success, failure: failure)
        case .LastMessage:
            return manager.GET("/api/last-message.json", parameters: parameters, success: success, failure: failure)
        case .Messages:
            return manager.GET("/api/messages.json", parameters: parameters, success: success, failure: failure)
        }
    }
}

enum Endpointでエンドポイントを列挙

APIのエンドポイントはSwiftのenumを利用して列挙します。Swiftのswitch文ではenumの全てのケースを網羅しないとコンパイルエラーになりますから、実装の漏れを防ぐこともできます。

上の例にはありませんが、共用型のenumとすることでエンドポイントのパスに文字列などを埋め込むことも簡単です。

enum Endpoint {
    case User(name: String)

    func request<T: MTLModel where T: MTLJSONSerializing>(manager: AFHTTPSessionManager, parameters: [String: String]?, handler:(response: Response<T>) -> Void) -> NSURLSessionDataTask {
        ...

        switch (self) {
        case let .User(name):
            return manager.GET("/api/\(name)/status", parameters: parameters, success: success, failure: failure)
        }
    }
}

ジェネリクスを使ってレスポンスに型付けする

public enum Response<T: MTLModel where T: MTLJSONSerializing> {
    case One(@autoclosure() -> T)
    case Many(@autoclosure() -> [T])
    case Error(NSError?)

    static func parse<T: MTLModel where T: MTLJSONSerializing>(JSON: AnyObject) -> Response<T> {
        var error: NSError?
        if let array = JSON as? [AnyObject] {
            if let xs = MTLJSONAdapter.modelsOfClass(T.self, fromJSONArray: array, error: &error) as? [T] {
                return .Many(xs)
            }
        } else if let object = JSON as? [NSObject: AnyObject] {
            if let x = MTLJSONAdapter.modelOfClass(T.self, fromJSONDictionary: object, error: &error) as? T {
                return .One(x)
            }
        }
        return .Error(error)
    }
}

ジェネリクスでMantleを利用する

APIからの結果を共用型のenum Responseで表します。JSONのオブジェクト、JSONの配列、エラー、の3つのパターンを共用型で表します。

@autoclosure() -> Tなどとなっているのは、T[T]にした場合に現在のSwift (Swift 1.1) のコンパイラが"unimplemented IR generation feature non-fixed multi-payload enum layout"というエラーを出すので、そのワークアラウンドです。将来のSwiftで解消されればもっと素直に書けるでしょう。

ここでstatic func parse<T: MTLModel where T: MTLJSONSerializing>(JSON: AnyObject) -> Response<T>という関数に着目します。これはMantleを利用してJSONから具体的なクラスのインスタンスを作るための関数ですが、具体的なクラスを書かずに型パラメータとして<T: MTLModel where T: MTLJSONSerializing>という風にTを設定しています。

func testStatus() {
    let expectation = expectationWithDescription("Status")
    client.status { (response) -> Void in

        switch (response) {
        case .One(let status):
            XCTAssertEqual(status().status!, "good", "Status is good")
        default:
            XCTFail("Response must have one status")
        }

        expectation.fulfill()
    }
    waitForExpectationsWithTimeout(10, handler: { (error) -> Void in

    })
}

テストコードにおける利用例

テストコードで結果を取得しているところをみると、switchで分岐して実際の結果を得ています。少し冗長に見えますが、例えばenum Responseに関数を追加することで、一般的なケースでより簡単に書けるようにできるかもしれません。また@autoclosure()しているためにstatus()となっているところがあります。

APIクライアント

public class APIClient {
    let manager = AFHTTPSessionManager(baseURL: NSURL(string: "https://status.github.com/")!)

    public func status(handler: (response: Response<Status>) -> Void) {
        Endpoint.Status.request(manager, parameters: nil, handler: handler)
    }

    public func lastMessage(handler: (response: Response<Message>) -> Void) {
        Endpoint.LastMessage.request(manager, parameters: nil, handler: handler)
    }

    public func messages(handler: (response: Response<Message>) -> Void) {
        Endpoint.Messages.request(manager, parameters: nil, handler: handler)
    }
}

APIClientの実装

これらの実装を利用することで、APIClientは上記のように簡単に書くことができます。JSONから変換されるクラスは、それぞれのhandlerに型パラメータとして指定されています。func lastMessageでもfunc messagesでも同じMessageクラスを型パラメータとして渡していますが、実際にはそれぞれResponse.One<Message>Response.Many<Message>が返ってきます。


こうしてできあがったAPIクライアントの実装をGitHubで公開しています。

APIClient.swiftAPIClientTests.swiftのふたつのファイルを見ると、おおよそどういった感じになっているか掴めるかと思います。

高機能になったenumやジェネリクスといったSwiftのモダンな言語機能を利用することで、Objective-Cよりもスマートに実装できます。Swiftの高い表現力でかっこいいコードを書いていきたいものですね。

スマートなおしらせ

本記事の内容は、筆者とid:yashigani_wが日常的なディスカッションを重ねた結果として生み出されました。私たちといっしょにSwiftでスマートにプログラミングすることに興味をお持ちの方は、こちらからエントリーください。


明日はid:astjさんです。

開発合宿で何を考えてどう作ったか

こんにちは。はてな社長の id:chris4403 です。
この記事ははてなエンジニアアドベントカレンダー2014の14日目です。元エンジニアということでカレンダーに参加させていただきます。

先日、はてなのサービス開発合宿が開催されました。
合宿の開催概要については、先日チーフエンジニアのid:onishiエントリを書いているのでそちらをご覧ください。

私もいちエンジニアとして参加したので、合宿の間、何を考えてどうやって作ったのかを書き記そうと思います。今後合宿に参加する人の何かの参考になればと思います。先に断っておきますが、あんまり技術的な話は出てきません。

day 1

AM

今回の合宿は、合宿の1週間ほど前に、作るサービスや機能の具体的なアイデアを持っている人が、参加者の前でプレゼンを行い、一緒に作る人を募るというチームビルドのフェーズがありました。私もそこに向けて、リッチメディアを扱うサービスのアイデアを考えていたのですが、実際はプレゼンの前にほとんどのチームがなんとなく出来上がっていました。私を含め数名がプレゼンを行ったのですが、そのときに私が獲得できたチームメンバーは、ゼロ。合宿に行く前にチームビルドに失敗してしまいました。無念。

ここでひとつの選択をすることになります。もともとの予定通り、リッチメディアを扱うサービス(スマホアプリ)をひとりで作るか、そもそも作るものをひとりでもなんとか形になりそうなものに変えるか、あるいは他のチームにジョインするか。

他のチームにジョインは、割りと全体的にチームがいい感じにできあがっていたので、そこにのこのこと参加するのも足手まといになりそうだなという予感があり、却下。結局、作るものを変更して合宿に臨むことにしました。
実際は、2日ほどひとりでアプリを作る方法を模索してみたのですが、リッチメディアをいい感じに扱いたいということで、Titanium Mobileでサクッと戦略が使えず、iOSとAndroidの両方をひとりで同時にやるのは不可能だと判断しました。

合宿初日

さて、いよいよ初日を迎えました。滋賀の合宿会場組は、朝から移動します。私は、はてなの京都オフィスで合宿に参加するグループでしたので、滋賀組よりも少し余裕をもって開始することができました。

合宿でサービスを作る場合、以下の2つの考え方があります。

  • 合宿が終わったあと、そのまま(多少の修正はある)リリースできる品質で作る
  • あくまでプロトタイプと割り切って、合宿が終わったらコードを捨てるつもりで作る

リリースまで狙いたいところでしたが、自分のプログラミング力と、期間、チームのメンバー数(ひとり!)を考えて、後者を選択しました。

言語は書き慣れたPerl、WAFはAmon2、O/R MapperはTengを選択。選択理由は以下の通りです。

  • Perl
    • サーバーサイドは書き慣れたPerlで。クライアント側はTitanium Mobileで作ることも一瞬考えましたが、iOSとAndroidで試せるようにするには、Webサービスの形にとどめておくのが良さそうと判断
  • Amon2
    • 過去にプロジェクトで使ったことがあり、個人でも手元でプロトタイプを作る際に便利に使用していたため
  • Teng
    • 使い慣れた技術でとはいえ、何かしらやったことがないものも取り入れたいという気持ちがあり、名前は知っていたけど使ったことがなかったため

Amon2は、ログインのプラグインではてなID(OAuth)のものが用意されており、簡単にログイン部分を作ることができました。環境を用意して、手元でログインできるまで30分ほどでした。

「テキストと画像が投稿できて、閲覧するユーザーはその投稿に対するフィードバック(良い/悪い)を投稿する」という簡単なサービスにしたので、投稿のエンドポイント作って、投稿してDBにデータを格納するというところまでで、初日の午前中は終了しました。

PM

ランチをオフィスで食べて、作業へ戻ります。
午後は、画像を投稿できるようにする作業。
画像の投稿の取り扱いですが、アプリケーションサーバーに保存するか、Amazon S3のようなストレージサービスに保存するか、データベースの中に保存するか、といくつか方法がありましたが、今回はデータベースの中に保存するようにしました。

データベースに画像用のテーブルを作成して、投稿用のページからPOSTされた画像を格納するようにしました。また画像の出力は、出力するページが1つしかなかったので、base64にエンコードした値をdataスキームでページに直接書き出すという方法を取りました。

また、投稿へのフィードバック用のテーブルと、エンドポイントを作成し、「いいね」「悪いね」のような評価を投稿できるようにしました。ここまでやって夕方くらいになったので、他チームで作業していた id:motemen にお願いし、開発用のサーバーにデプロイできるように設定してもらいました。
合宿では、最終日に作ったもののプレゼントデモを行うようになっており、その際にみんながサービスを触れる状態にしておく必要があります。合宿2日目に入ると、各チーム作業が白熱してくるので、初日のうちにデプロイ出来る状態にしてもらうという作戦でした。

普段デプロイ作業に慣れているだけあり、1時間もしないうちに、手元のiPhoneからテスト環境にデプロイされたサービスを閲覧できるようになりました。

自分でもデプロイ出来るように手順をまとめたところで、夜の懇親会タイムに突入。会社のセミナールームに寿司とビールが運ばれてきました。少し寿司をつまんで、各チームの進捗をなんとなく把握したところで、作業へ戻りました。このときに、他チームで企画として参加していたid:sawaccioに、テスト環境のURLを教えて、ちょっと使って感想を聞かせてとお願いしておきました。

他のチームは夜遅くまで色々とやっていましたが、私は帰宅。テスト環境のサービスを触りながら、翌日どういうことをしていくかをまとめます。この時点でシステム的な不具合もいくつか見つかっていました。

  • 投稿した文字が化ける
  • 大きな画像が投稿できない

day2

AM

出社して、いきなり合宿モードに。まずは不具合の解決に着手します。
前日あたりをつけていましたが、投稿した文字が化ける問題は、テスト環境に作成したデータベースの文字コードの問題でした。ローカル環境ではutf8だったものが、テスト環境ではlatin1で作成されていたので、テスト環境のテーブルをutf8で作りなおしました。これで文字化けは解決。

画像の投稿は、昨夜懇親会のときに、他チームのエンジニアに「デプロイしたんだけど、大きな画像の投稿ができないんだよね」というような相談をしたところ、proxyの設定ではないかというアドバイスをもらったので、早速チェック。やはり、テスト環境ではPOSTされるデータサイズにproxyで制限をかけていたので、当該サービスのproxyの設定を修正しました。nginxの設定ファイルに以下の1行を加えます。

    client_max_body_size 1024m;

設定を反映したところ、POSTがサーバまで到達しましたが、Internal Server Errorが発生してしまいました。画像を格納するテーブルのカラムサイズが足りないのが原因だったので、blob型だったカラムをmediumblob型に変更しました。
反映後、画像投稿を確認した所、再度エラー。今度はMySQLのmax_allowed_packetのサイズを超えているというエラーでした。
テスト環境のデータベースを確認すると、max_allowed_packetのサイズが確かに小さかったので、大きな値に変更しました。

mysql> set global max_allowed_packet = 4194304;

再度、画像投稿を確認したところ、正常に投稿できていました。本当はmy.cnfに記述するのが恒久的で良いのでしょうが、楽をしてしまいました。

色々とはまりながら、ここまで進んで午前中が終了でした。

PM

お昼前に、昨日テスト環境を教えたid:sawaccioから、フィードバックは「いいね」「悪いね」のような形ではなく「コメント」の方がいいのではというアドバイスをもらいました。ちょっと悩みましたが、サービスのコンセプト的にももう少しコミュニケーション要素があったほうが良さそうという感触だったので、テーブルを増やし機能を追加しました。このとき、もともとあった「いいね」「わるいね」の機能は削除することにしました。

そうこうしていると、id:onishiの合宿の記事にある「合宿新聞」に、合宿のプロダクトのリリース報告をするチームが現れました。発表前に使ってもらおうという作戦ですね。私も、言葉で説明するよりも触ってもらった方が良いサービスだったので、同じように「合宿新聞」で告知を行いました。

合宿を盛り上げるため、また合宿に参加してない人も巻き込んで会社全体で盛り上がるため、「合宿新聞」という企画を考えてみました。

サービス開発合宿 - Hatena Developer Blog

社内リリース後は、細かい不具合や、デザインの調整などを行い、帰宅。

day3

AM

午後から合宿の成果発表会なので、プレゼン資料づくりから。
そんなに言葉で説明するものでもないので、30分もかけずにさらっとつくって、発表の準備はOK。つづいて、発表ギリギリまサービスの微調整。昨日社内リリースしたおかげで、何人かの方が使っていただいて、2〜3人で使っていたときよりも、もう少し利用イメージが湧くようになりました。もう少しここはこういう情報出したほうがいいな、ここは出さないほうがいいなという判断をして、ギリギリまで手をいれていきました。

PM

午後は、滋賀の合宿所から戻ってきたメンバーと合流して発表会。発表のあとは、1人3票で投票を行い、順位を決めました。
私のサービスは、1票しか入らず、残念ながら最下位。他のチームのクオリティと比べても、まあ妥当な順位かなと思いました。

その後、Twitterで社員の方から励ましの言葉をかけていただけたのは嬉しかったです。

まとめ

3日間の開発合宿で、どのタイミングでどういうことを考えながらサービスを作ったのかを簡単に振り返ってみました。
そのままリリースするプロダクトを作らない場合、つまりプロトタイピングに特化した合宿にする場合は、何を試したいのかフォーカスする部分をしっかりと考え、作るものと作らないものをうまく切り分けて時間を使うのが良いと思います。ひとりだと、「こういうのつくろう!」と勢いをつける自分と、「本当にそれいるの?」と冷静になる自分をうまくコントロールしないといけませんが、複数人で合宿する場合は、ワイワイやりながら取り組めるので良いですね。次の合宿は誰かと組みたいと思いました。

はてなでは、社長と開発合宿を過ごしてくれるエンジニアを募集しています。


採用情報 - 株式会社はてな

Monitoring Kubernetes

CTOのid:stanakaです。

この記事ははてなエンジニアアドベントカレンダー2014の13日目です。(ちなみにもう一度登場予定です。)

昨日、gcp ja night #29 (Google Cloud Platform (GCP)の話を肴にピザとビールをいただく会)Kubernetesのmonitoringについて話してきました。

Kubernetesとは

KubernetesはGoogleが開発している複数のDockerコンテナを協調動作させることのできるクラスタ管理ミドルウェアです。Kubernetesは今年の夏前にオープンソースとして公開されたばかりということもあり、まだまだ荒削りなところがあります。プロダクションに入れるには時期尚早ですが、2015年には完成度も高くなってくることが期待できそうです。

まずプロダクションに入れる際には必須となるリソース状況のMonitoringについて調べてみました。詳しくは上記のプレゼン資料に書いてあります(といってもそれほど詳しくないです)が、以下にまとめてみます

Monitoring Kubernetes

Kubernetesのクラスタは、Master componentsとMinion(クラスタのノード)から構成されます(下図参照)。

このクラスタのMonitoringをするには各Minionの状況とそれらの内部で動いているDockerコンテナの状況を知る必要があります。Kubernetesが持つmonitoringに関連する構成要素は、kubelet, cAdvisor, heapsterの3つで、それぞれ以下の役割を持ちます。

  • kubelet .. 各Minionで起動するプロセス。REST APIにより色々な制御、情報収集ができる
  • cAdvisor .. Dockerなどのコンテナのリソース状況を収集するツールで、kubeletと協調動作します。
  • heapster .. クラスタ内の各Minionから情報を収集し、InfluxDBへインポートします。

Kubernetes標準ではheapster + InfluxDB + Grafana による構成が用意されており、以下のような画面です。

heapsterは各Minion上で動作しているkubeletにリソース情報を問合せています。例えば以下のようなAPIを叩きます。

$ curl http://10.240.153.49:10250/stats/ | jq 'keys'
[
  "name",
  “spec",
  “stats",
  "subcontainers"
]

statsにリソース情報が入っており、60秒分のcpu, memory, network, filesystem, ioの情報が入っています。

各Minion上で動作するDockerコンテナ単位のリソース情報を収集するには、以下のようにすることで指定したコンテナに関する情報が得られます。

curl http://10.240.56.195:10250/stats/90559c9f-8123-11e4-a0ec-42010af08f91/php-redis

KubernetesではあるDockerイメージを複数のDockerコンテナをMinionで動かすことができ、それらはPodsという概念で管理されています。どこのMinionでどのPodsに関するコンテナが動いているかは以下のようにMaster componentsに問合せることで分かります。

$  cluster/kubecfg.sh -json list pods
{
  "items": [
    {
     "id": "73f777c6-8123-11e4-a0ec-42010af08f91",
     "host": "kubernetes-minion-4.c.buoyant-zodiac-558.internal",
     ...
      "desiredState": {
        "manifest": {
          "containers": [
            {
              "image": "brendanburns/php-redis",
              "name": "php-redis"
              ...

これで得られた情報から各Minionのkubeletに問合せを行うことでリソース情報は一通り収集できるようになります。

この先には得られた情報からオートスケールもさせたくなるのですが、そのためには動的にコンテナ数を変更する必要があります。これはkubecfgでresizeを実行することで実現できます。

$ cluster/kubecfg.sh list replicationControllers
Name                   Image(s)                   Selector            Replicas
----------             ----------                 ----------          ----------
frontendController     brendanburns/php-redis     name=frontend       3

$ cluster/kubecfg.sh resize frontendController 4

$ cluster/kubecfg.sh list replicationControllers
Name                   Image(s)                   Selector            Replicas
----------             ----------                 ----------          ----------
frontendController     brendanburns/php-redis     name=frontend       4

おわりに

Kubernetesはまだまだ荒削りなソフトウェアですが、クラスタ設計やコンテナ配置などにGoogleのこれまでのコンテナ運用に関するノウハウがこめられており、非常に興味深いプロダクトです。実際にプロダクション環境に入れるかどうかは置いておいて、その思想や実装方針などは一通り掘り下げて見ておいたほうが良いと思っています。

【Alamofireを読む】メソッドチェインと遅延実行の実装

こんにちは。アプリケーションエンジニアのid:yashigani_wです。 この記事は、はてなエンジニアアドベントカレンダー2014の12日目の記事です。

Swiftの登場から約半年。 はてなでは、徐々にSwiftへの移行を進めています。 今回はSwiftでHTTP通信を簡単に実装することができる、Alamofireというライブラリの実装についてのお話です。

AlamofireはObjective-CのHTTP通信ライブラリとして圧倒的支持を誇る、AFNetworkingの作者であるMattt Thompson氏によるプロダクトです。 いち早くSwiftに対応したものであるということだけでなく、簡潔な記述ができることもあって注目を集めています。 このAlamofireを使うと、通信の処理を以下のように書くことができます。

Alamofire.request(.GET, "http://b.hatena.ne.jp/entry/json/?url=http%3A%2F%2Fdeveloper.hatenastaff.com%2Fentry%2F2014%2F12%2F01%2F164046")
.response { (_, response, _, _) in
  println(response.statusCode)
}
.responseString { (_, _, string, _) in
  println(string)
}
.responseJSON { (_, _, JSON, _) in
  println(JSON)
}

メソッドチェインで遅延実行されるコールバックのclosureを書き連ねることができ、モダンな雰囲気がします。 しかし、これは一体どのように実装されているのでしょうか? 内部でclosureを保持しているのだろうという予想はできますが、いたってシンプルな実装になっておりおもしろいです。

メソッドチェイン・遅延実行の秘密に迫る!

AlamofireではひとつのHTTPのリクエストに対してRequestを作成します。 RequestNSURLSessionTaskRequest.TaskDelegateを持ち、Request.TaskDelegateNSURLSessionTaskDelegateを実装しています。

RequestresponseStringresponseJSONの処理は最終的にresponseメソッドに集約します。 ということで早速responseメソッドの実装を見てみましょう。

public func response(queue: dispatch_queue_t? = nil, serializer: Serializer, completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {
    // `Request.TaskDelegate`の`queue`にコールバックの実行を登録
    dispatch_async(delegate.queue) {
        let (responseObject: AnyObject?, serializationError: NSError?) = serializer(self.request, self.response, self.delegate.data)

        dispatch_async(queue ?? dispatch_get_main_queue()) {
            completionHandler(self.request, self.response, responseObject, self.delegate.error ?? serializationError)
        }
    }

    return self
}

Alamofire/Alamofire.swift at 445a4ec908aa2b44b13602b2b71e99378084293f · Alamofire/Alamofire · GitHub

当然ですが、responseメソッドはRequestオブジェクト自身を返すのでメソッドチェインを使って処理を書き連ねることができます。 注目したいのはdelegate.queueに、completionHandlerを呼び出すclosureを登録する処理です。 どうやらこれが遅延実行の秘密です。

さらに秘密を紐解く

では、delegate.queueとはどのようなものなんでしょうか? delegateの正体は、Request.TaskDelegateというNSURLSessionTaskDelegateを実装したオブジェクトでした。 Request.TaskDelegatequeueに迫ってみると、以下のようになっています。

class TaskDelegate: NSObject, NSURLSessionTaskDelegate {
    let task: NSURLSessionTask
    let queue: dispatch_queue_t

    // 省略...

    init(task: NSURLSessionTask) {
        self.task = task
        self.progress = NSProgress(totalUnitCount: 0)
        // `dispatch_queue`を生成し、suspendする
        self.queue = {
            let label: String = "com.alamofire.task-\(task.taskIdentifier)"
            let queue = dispatch_queue_create((label as NSString).UTF8String, DISPATCH_QUEUE_SERIAL)

            dispatch_suspend(queue)

            return queue
        }()
    }
}

Alamofire/Alamofire.swift at 445a4ec908aa2b44b13602b2b71e99378084293f · Alamofire/Alamofire · GitHub

なんてことはなく、ただのdispatch_queueです。 しかし、イニシャライザでdispatch_queueを作ったと同時にsuspendしているのがなんとも意味深です。 これを踏まえて、通信の終了時の処理であるURLSession(session: NSURLSession!, task: NSURLSessionTask!, didCompleteWithError error: NSError!)の実装を探してみましょう。

Alamofireでは、NSURLSessionManagerが保持し、NSURLSessionDelegate/NSURLSessionTaskDelegateManager.SessionDelegateに実装されています。 Manager.SessionDeleagteURLSession(session: NSURLSession!, task: NSURLSessionTask!, didCompleteWithError error: NSError!)taskに紐付いたRequestオブジェクトを取り出し、そのdelegateに終了を通知します。

ややこしいですが、つまるところRequest.TaskDelegateURLSession(session: NSURLSession!, task: NSURLSessionTask!, didCompleteWithError error: NSError!)の実装を見ればいいということです。

func URLSession(session: NSURLSession!, task: NSURLSessionTask!, didCompleteWithError error: NSError!) {
    if error != nil {
        self.error = error
    }
    // 通信が終了したのでsuspendしていたqueueをresume
    dispatch_resume(queue)
}

Alamofire/Alamofire.swift at 445a4ec908aa2b44b13602b2b71e99378084293f · Alamofire/Alamofire · GitHub

suspendしていたqueueをresumeしています。 つまり、suspendしているqueueにclosureを登録し、通信終了時にresumeすることで遅延実行していたんですね。 普通にやるとclosureの保持は複雑で嫌な実装になりそうですが、GCDをうまく使うことで驚くほど簡単に実装されています!

まとめ

AlamofireはAFNetworkingのMatttさんのプロダクトということもあり、便利なライブラリというだけでなくコードからも多くのことを学ぶことができます。 今回紹介したコールバックの遅延実行以外にもおもしろい実装がたくさんあります。 AFNetworkingと比べ実装も小さなものになっていますので年末年始の暇な時間にコードリーディングしてみてはいかがでしょうか。

明日はid:stanakaさんです。

参考リンク

素敵なお知らせ

Swiftでアプリを開発したいエンジニアの方はこちらからエントリーしてください!

iPhone、Androidアプリエンジニア職 - 株式会社はてな