廃墟

本ブログは更新を終了しました。 技術的な記事のみ、有用性を鑑みて残しておきます。

Angularが悪いんじゃなくて使い方が間違ってるんだという話

最近いろんな案件や記事でAngular.jsを耳にしますが、結構「あれ使いにくいよね」などという話もちらほら。 実際ぼくが使っていてもなんだか使いにくく感じることがままあります。

多少触ってみたり考えてみたりして、僕なりの結論めいたものが見えてきたので記事にしてみようとおもいます。

サーバーサイドレンダリングとクライアントサイドレンダリング

perl, PHP全盛期

古来(?)、perlPHPの時代、多くのWebページは手打ちのHTMLで出来ていました。 この中で、フォームなどの機能をちょっとだけ追加したい、などの要請から、HTMLに直接ちょっとだけコードを埋め込めるPHPが一斉を風靡しました。

Rails全盛期

PHPによるWebアプリケーションが盛んになってくると、そのプログラムに重複する部分が多いことや、設計・実装をパターン化することで効率化が図れることがわかってきて、Ruby on RailsなどをはじめとするMVCフレームワークが登場しました。

大事なのは、ここまでずっと「サーバー側でHTMLを生成して、動的な要素はその中に埋め込まれていた」ということです。

Twitterの公開APIスマートフォン向けアプリの拡大、プラットフォームの多様化

TwitterAPIを公開し、サードパーティが自由に対応アプリを開発できることが、多様なアプリケーションを生むようになりました。 これとほぼ同時期にスマートフォンが普及し、そういった複数プラットフォームへの対応が要請されるようになりました。

その中では、すべてのWebでやろうという流れも強く、未だにその流れもありますが、レスポンスの悪さなどから好かれない傾向にあるようです。

補足

  • そもそもゲームとかだとRPCが当たり前のようにやられてたので、そこから輸入した考えではある
  • スマホの台頭以前からデスクトップアプリでは必要なことでしたが、やはりスマホ以後で大きくこの状況が動いたというのはあると思います。

SPAという提案 (Single Page Application)

スマートフォンアプリが発達してきたことで、Webサービスというものの捉え方は変わり、(大抵はJSON+HTTPの)APIこそがWebサービスである、という考え方が出てきました。 また、Webページより先にアプリ側を作ることも増えてきたので、じゃあどうせなら、アプリ側でシッカリ作ったAPIをWebでも使えたら、セキュリティの問題もHTMLよりは考えやすくなるし、良いことづくめじゃないか。 じゃあ、もうページの遷移すらやめてしまって、全ページをJSでレンダリングするという考え方で作ってもいいんじゃないか。 HTML/CSS/JSはクライアントサイドのもので、サーバーとはAPIで通信する。HTML/CSS/JSは静的なサーバーから配信すれば十分高速だろうし、API通信で重い部分はきちんとUIで解消していけばいいじゃん、行ける行ける!

…と、SPAが生まれるまでは、こんな流れなんじゃないでしょうか。

結論「AngularのSPA的アプローチと静的HTML生成アプローチの混在が混乱を招く」

さて、そういわけで結論にすっとばします。

Angularなどを使ったSPA的なアプローチは、バックエンドが全てJSONAPIで構成されていることを前提にしている。 だから、APIベースで考えずにviewレイヤでHTMLをレンダリングする過去のアプローチと混ぜてはいけない。 SPAで重要なのは、API側とビュー(クライアント)側が疎結合になり、それぞれ独立して開発を進められるようにすること。

わかってる人には当たり前のことなんだと思うんですけど、今のところの自分の解釈をざっと書いてみました。 この辺り、まだまだ流動性の高い部分なのでこれからも変わっていくと思います。コメントもお待ちしておりますので、いろいろ考えを深められたら、と思います。

RailsやMiddleman で自動的に目次を作るにはどうすればいいのか

せっかくなのでブログでも共有しておく。

お世話になってる先輩が泣いているのは忍びなかったので適当にサンプルコードをつくった。

考え

リポジトリにも書いたんだけど、以下の様な感じで考えて作った。何かの参考になるかもしれないので公開しておく。

目次の責任範囲はどこにある?

  • テンプレートエンジン、ジェネレータ
    • そういう考え方もある
  • h2の代わりになるタグをヘルパで定義して使う
    • Dirty! Dirty! Dirty!
    • 再利用性低すぎ!
  • レイアウトファイル
    • あ、行けそう

ということでレイアウトファイルが目次を作るのはそれなりにアリだと思います。

拡張の仕方

  • レイアウトファイルの中の処理をゴリゴリ書く
  • レイアウトファイルの中の処理をHelperに切り出す

例:ヘッダへのリンクが貼りたい

  • ヘッダにname要素やid要素をつける (どうせセマンティック的には自分でつけるべき)
  • パースした方でゴリ押し

ISUCON4 予選に出てきた

去年に引き続いて今年もISUCON4の予選に出ました。

去年は学生だったので @cnosuke , @rkmathi と一緒に出てたのですが、 今年は学生じゃないので @syu_cream , @suma90h と一緒に出ました。チーム名は「SSS」 (僕の本名、彼らのアカウント、の頭文字がすべてSなので。) 僕がタイミング悪く沖縄に出張していて、合流出来ずリモート参戦でご迷惑おかけしちゃいました。

前回はプログラムの方にいろいろとバグがあったのですが、今回はコード自体は普通に動くようにできていたという印象です。 寧ろ、改変したSQLにバグがあったりして3時間ぐらいドブに捨ててしまったのが痛かったです。 8時間って短いですねぇ、やっぱり。

mysqlのCPU負荷が異常に高かったのでインデックスを貼ってみた所で劇的に解決しました。 コードの改変より先にそっちをやれば良かったですね。

16:50時点でのスコアが4426しかなくて、こりゃあお通夜だなーと思っていたら、後半いろいろ頑張ったら結果としては25348ぐらいまで向上しました。 それほどインフラ特化じゃないプログラマーのチームとしては、悪くないんじゃないでしょうか。

主にやったこと

  • nginx チューニング
    • syu_cream がやってくれた
    • 静的ファイルはnginxで返す
    • worker_processは1で同時接続数は1024ぐらいにしてた
    • gz圧縮の話もあったけど、ベンチマークがローカルで動いていることから逆効果かなと思って止めた
  • mysql のパラメータチューニング
  • mysql へのインデックス追加
    • わりと順当に login_log の ip と user_id それぞれにインデックスを追加しただけ
  • unicorn のプロセス数と mysql の max_connections のチューニング
  • puma への変更
    • 最終的にスレッド5つ、プロセス4つに落ち着いた
  • TCP(OS)のパラメータチューニング
    • "dial tcp 127.0.0.1:80: cannot assign requested address" エラーへの対処
    • sysctl -w net.ipv4.tcp_tw_recycle=1
    • (syu_cream/suma90hがやってくれたから良く分からないけどエラーが止まった)

やってみたかったけどできなかったこと

  • MySQLのvarchar(255)になっているカラムの長さを調節
    • login_log の login とか、 (24) にすれば良かった気がする
    • booleanで良い部分が tinyint(4) になっていた所も似たような話
  • ベンチマーク直前のウォームアップ時
    • MySQLのキャッシュを暖める
    • MySQLのコネクションをあらかじめ貼っておく
  • きちんとボトルネック解析
    • 結局、せいぜいtopを見る程度で、細かくボトルネックを解析できなくて、場当たり的でした

感想

去年も言ったけど、こういうパフォーマンスチューニングも面白いですねぇ。 勉強になるので、来年も参加したいなーと思っています。

一緒に参加してくれた @syu_cream さん、 @suma90h さん、ありがとうございました!

DTI@VPS 上のRailsからSendGridを使うとタイムアウトする問題の解決策

Railsからのメール送信にSendGridを使う様に変更してたら起きた問題。誰かの役に立つかもしれないので、メモ。

遭遇した現象とか雑多気味なメモ

SendGridの通例通りsmtpの設定をしてみた、けれどうまく送信されないという現象に遭遇。 同じコードをローカルで立ち上げるとちゃんとメールが送られる。なんじゃこりゃ。と。

動作を見る限りでは、タイムアウトの気配なので、おもむろに以下の様に確認した。

$ telnet smtp.gmail.com 465

まずビンゴで、telnetが通っていない事が判明。

試しにufw disable すると、上記のtelnetが通った。 → 原因がファイヤーウォールに搾られた

次に iptables -L してやると、ESTABLISHED,RELATEDが読まれていない事が確認できる。何か変だぞ、と。 他の似たような設定を為ている環境でiptables -Lした結果と全然ちがう。 ここで、試しに以下の様にしてみる。

iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

すると、きちんと繋がる。

(しかしここではufw経由で設定出来ないと困るなーと思っていたら、うっかりiptables -FしちゃってサーバーからSSHがBANされたので、一回VPSの管理コンソールから再起動。)

その他の途中で見てた不穏なエラーメッセージ

検索でだれかが行き着くように、遭遇した不穏なエラーメッセージを書いておく。

problem running ufw-init
# とか…
iptables: Bad rule (does a matching rule exist in that chain?).
# とか…

結論

どうやら OpenVZ環境でufw を使っていると起きるらしい。 (DTIはOpenVZ、らしい)

そういうわけで、以下のサイトの内容の通りにしたら上手く動作してくれました。

Angular.js と Rails の form_for を良い感じに連携する

最近とある案件で Angular.js を使い始めたのですが。
どうにもRailsと相性がそこまで良くないというか、2つの世界をCoCで繋ぐ決定的なgemなどが出てきていないからか、不便さを感じます。

そんな中で、特に不便に感じたのが、form_for がロクに使えないことでした。

$ rails g model post title:string

みたいなモデルを想定してください。

愚直にやると、Angualr.jsで非同期にPOSTする時のformは、こんな感じになってしまいます。

# slim記法の例
div 'ng-controller'=>'PostCtrl'
  = form_for(@post, html:{ 'ng-submit'=>'submit()' }) do |f|
    = text_field :title, 'ng-model' => 'post.title'
    = button :submit
  end

うーん、何かこう、いけ好かないですよね。 同じ様な事を何回も書いてるし…

ということで、こんなのを作ってみた。

# /app/helpers/angular_form_builder.rb

# http://uzuki05.hateblo.jp/entry/2013/04/05/114356

class AngularFormBuilder < ActionView::Helpers::FormBuilder
  def ng_text_field(method, options = {})
    options[:html] ||= {}
    @template.text_field(@object_name, method, objectify_options(options[:html].reverse_merge!(ng_options(method))))
  end

  # TODO: text_field 以外の互換メソッドも用意しましょう

  private

  def ng_options(method)
    {
      'ng-model' => "#{@object_name}.#{method}",
    }
  end
end
# /app/helpers/application_helper.rb

module ApplicationHelper
  def ng_form_for(*args, &proc)
    args << {} unless args.last.is_a?(Hash)
    options = args.last
    if args.first.is_a?(Symbol)
      options.merge!(as: args.shift)
    end
    options.reverse_merge!(
                             builder: AngularFormBuilder,
                             format: :json,
                             html: {
                               'ng-submit' => 'submit()'
                             }
                           )
    form_for(*args, &proc)
  end
end

これを使うと、フォーム部分は以下の様に書けます。

# slim記法の例
div 'ng-controller'=>'PostCtrl'
  = ng_form_for(@post) do |f|
    = ng_text_field :title
    = button :submit
  end

あとは、Angular.jsのControllerをこれに準拠して書いていけば、それなりに柔軟さを保ちつつ、RailsのFormBuilderだけで概ね記述できて良いんじゃないかな、と思います。

ちなみに、CSRF対策については以下のStackoverflowでの議論に準拠すると良いと思います。

angularjs - Rails CSRF Protection + Angular.js: protect_from_forgery makes me to log out on POST - Stack Overflow


Angular.js触って2日目ぐらいなので、もっと良い方法あったらおしえてください!

AFNetworking 2.2あたりから中間証明書が必須になった

今作っているiPhoneアプリで、 pod update をしただけで、一切のAPI呼び出しが出来なくなった。
curlコマンドを打っても、GETで叩ける部分をChromeで呼んでも、何も問題ないので、サーバーは正常?に思える。

多少は分かりやすいエラーが出てるかと思ってエラーメッセージを見てみても、-999ステータスで、TaskがCancelされたみたいなことしか出てこない。 cancelなんかしてねーよ??

結局、SSLの設定状況をチェックしてくれるサイトでAPIサーバーをチェックしてみたら、中間証明書がサーバーから提供されてないよってエラーが… これかーーーー!

サーバー側を直したら問題なく通信出来るようになりましたとさ。

新規案件を請ける時に気をつけていること

今回は、フリーランス専業になって数ヶ月経過した僕が、新しく案件を請ける時に気をつけた方が良いな、と思っている事を書いてみます。
おそらく、フリーランスに限らず、社内プロジェクトに新しく参加した場合とかでも一緒なんだと思います。 普通の会社に在籍したことがないから良く知らないけど。

よくこの手の記事では、契約の範囲や賃金などについて語られますが、ここではそういった事には殆ど触れていません。(僕も詳しくないんだ、すまない。)

ちなみに、請け負うプロジェクトが以下のことに当てはまる場合、特に顕著に効いてくると思います。

  • 在宅/リモートワークである
  • 今まで一緒に仕事をしたことのある人が少ない
  • 緊急の案件である

1. プロジェクトの状況を把握する

プロジェクトが完全に初期の空白状態、という事は稀です。どんなにまっさらなプロジェクトでも、以下の様な事が決まっているはずです。

  • ターゲットとなる顧客
  • 与えたいベネフィット
  • 大まかな規模感

もし、これら決まっていない場合、まずはそこから決めないとプロジェクトが失敗するのは明白なので、促しましょう。

また、もう少し詳しいものだと、他の人がプロトタイプを作ったことがあったり、既にデザイナーへの発注などによって画面の基本構成が決まっていたり、まぁ多種多様です。
とにかく、現状把握には多少の時間がかかります。

必要以上に情報を隠したがる方とは仕事をしない、というのも視野に入れて良いと思います。 仕事しにく過ぎるので。

1.1. 人的な現状を把握する

既に参加している人に、プロジェクトに関係する人と担当範囲を列挙してもらいましょう。
スキルセットや契約の形態まで聞けるとベターです。 他にやっていることがあって片手間の人かもしれません。 あなたより経験の浅いエンジニアかもしれません。

ちなみに、発注者がプロジェクトに関する全容を知らなかったりするケースは、嫌な予感がぷんぷんするので、慎重に掘り下げて聞いていく必要があります。

1.2. 技術的な現状を把握する

プログラムやサーバー構成について、なるべく広い範囲にわたって把握しておいた方が良いです。
後から聞くのは面倒臭いので、一気に構成図とかを書けるぐらいまで聞いてしまうのがベターです。

2. これからの動きを明確化する

監査にきたのではなく仕事をしにきたのだから、現状が分かったら、これからの動きについて計画を立てていく必要があります。

2.1. 連絡手段・頻度を明確化する

Skypeが良いのかメールが良いのか、定例で事務所に行くのか、常駐なのか。
常駐だとしても、ミーティング頻度はどうなのか。 他のメンバーも常駐なのか。
そういった事もハッキリさせておくと良いです。

2.2. 担当する作業の範囲・期間を明確化する

ここまで出来てやっと、自分が担当する作業の範囲や納期を見積もることが可能になります。ここの順序を間違えないでください。おそらくは金額的な見積もりが出来るのもこの段階まで来てからです。

また、だいたいプログラマーは自分の能力を過信するので、自分の作業量はほどほどにしておきましょう。 1日のうち集中して作業出来る時間は3〜4時間程度だと思っておくと良いです。(実際そうだろ?えっ、そうじゃない?お前みたいなデキる奴のことは、知らん!)

2.2+. 周りと協調して動けるか想像する

担当作業をやっていればプロジェクトは完遂するでしょうか?以下の様な類いの落とし穴に気をつけてください。

  • モバイルアプリを作っているなら、サーバーサイドのAPIはどうするのか。
  • 追加機能開発なら、以前の機能や設計に問題があった場合にどうするのか。
  • 完全に新規の開発だとして、仕様の変更はどの程度、出てきそうか。
    • 現時点での不明瞭な仕様は誰が明確にするのか。自分?発注側?
  • 今後、自分が抜けた後で誰がメンテナンスするのか
    • ドキュメントをどのくらい整備すべきなのか

2.3. 計画を立てる

マイルストーン設定、ガントチャート、まぁそういうもので開発のスケジュールを立てましょう。
発注元のマネージャーが優秀でやってくれるなら任せても良いですが、大抵そんなことはないので、諦めて自分の方で明確化しちゃった方が楽です。(※個人差があります)

プロジェクトを完遂する

あとはプロジェクトを完遂しましょう。

3.1. プロジェクトが動き出したら、積極的に動く

自分の動く範囲は確かに明確化した通りなのですが、多少は「おまけ」しても良いでしょう。
自分が参画したプロジェクトが失敗に終わるよりは、成功した方が箔がつきます。
ついつい自分の作業範囲を小さく見積もりたくなりますが、先方の期待とはズレている可能性がありますから、動かしながら意識の摺り合わせをするのが良いでしょう。

おわりに

駆け足でしたが、数年間、いろいろなプロジェクトをやってきて思う所をざっとまとめてみました。

おかげさまで9月末までは、ほとんどイッパイイッパイですが、何かお仕事の相談などありましたら、お気軽にどうぞ。