chefに疲れたあなたにはItamaeがおすすめ!
chef使うのめんどくさくないですか?
私はいままでサーバーの設定を自動化するためのツールにchefを使っていました。 最初chefを使い始めた時は、いままで手作業でやっていたことがコード化・自動化できるので「お、便利ー!chef最高!」という感じだったのですが、 だんだん使っていくうちにchefが高機能が故に自分の用途としては使うのが辛くなってきました。
そもそも自分はchef soloしか使わない
chef は基本的にはchef-server - chef-client という構成で、設定を自動で行いたいサーバーに対してchef-clientをインストールします。 そして対象のサーバーの設定をコード化したrecipeを保持するchef-serverとchef-clientが通信して対象サーバーにrecipeを適応します。
このchef-serverを立てずにchef-clientだけでサーバーの設定を自動化するchef-soloという機能があり、それは自分の手元のマシンからSSH経由でchef-clientにrecipeを送信・適応させるのですが自分の場合はそもそもこれしか使っていません。
管理しているサーバーが何十台もあって常に稼働している場合はchef-serverがあって自動でrecipeを適応させる機能も価値があるかもしれないですが、自分の場合は管理対象のサーバーがそもそも多くないからです。
覚えることが多過ぎる
一つのサーバーの設定を管理するchefのレポジトリを作るためにchefだけでなくなく、コミュニティが作ったrecipeの依存管理をするBerkshelf、chef-soloを使うためのコマンドラインツールknife-soloなど、なんだかんだで必要なツールを覚えるために結構な学習コストがかかります。
最近はchef-soloではなく、chef-zeroだと言い出している
自分はそもそもchef-soloしか使っていないのに、そのchef-soloが終わりchef-zeroを使って欲しいそうです。
詳しい内容は以下のブログを参照してください。
From Solo to Zero: Migrating to Chef Client Local Mode | Chef Blog
いまからまた新しいchef-zeroとかのプラクティスを学ぶのが辛いです。。。
もっとシンプルなツールが欲しい!
chefは大規模にサーバーを管理している人にとっては学習コストに見合うメリットがあるツールなはずだけど 、自分にとってオーバースペック過ぎました。 もっと単純に、ホストマシンでコマンド実行したら対象サーバーにrecipeを適応するだけの、シンプルなツールが欲しいと思って調査し始めました。
Itamaeはシンプルで使いやすい!
ItamaeはchefのようなDSLで、 よりシンプルな機能だけを実装したツールです。
たとえば、以下のようにhttpdをインストールするrecipeを作成します。
package 'httpd' do action :install end
このrecipeを対象サーバーに適応するには以下のコマンドを実行します。
$ itamae ssh -h 192.168.10.10 -p 22 -u user -i /path/to/private_key recipe.rb
オプションが多いですが、やっていることは明白で-h で対象サーバーのホストを指定し、-pでポートを指定、-uで接続ユーザーを指定、-i で秘密鍵を指定、最後に適応させるrecipeファイルを指定します。
chefのようにnodeによって値を変えたい場合は、以下のように-jオプションで利用するjsonファイルを指定すればいいだけです。
$ itamae ssh -j node.json -h 192.168.10.10 -p 22 -u user -i /path/to/private_key recipe.rb
ちゃんと知りたい場合はGithubレポジトリのwikiを見れば、推奨されるベストプラクティスから使い方までちゃんと記載されていますし、量も少ないのですぐに使えるレベルまで学習できます。
自分のようなchefに疲れた人にとって、itamaeはchefで学んだ知識もある程度活かせ、かつ簡単に使えるベストなツールです!
さらに嬉しいのはchefは対象サーバーにruby + chefをインストールしないといけないですが、itamaeはその必要もないのです。
もし他にもchefに疲れた人がいらっしゃれば、ぜひitamaeをオススメします。
Vagrant、chef、 Dockerって最近よく聞くけどなにが違うの?
はじめに
先日うちの会社のアルバイトに来てくれている大学生さんに 「Vagrant, chef, DockerってよくWebで記事を読むんですけど使っている文脈が似ていて、何が違っていてどう使い分けるのかよくわからないんですよね〜」という風に尋ねられました。
その時に彼にも口頭で説明はしたのですが、うまく説明出来たのか自分でも怪しかったので今回まとめてみたいと思います。
Vagrantとは
Vagrantについて、 Vagrant日本語ドキュメントには以下のように説明されています。
Vagrantは、 あなたとあなたのチームの生産性/柔軟性を最大限にする手助けをするために、 簡単な環境設定/複製可能なマシン/業界標準の技術で構築された1つの一貫したワークフローによって制御される可搬的な開発環境を提供します。
その恩恵を得る為に、Vagrantは先人の知見に基づいています。 マシン(開発環境のマシン)は、ViretualBox, VMWare, AWS, そのほかのプロバイダにより提供されます。 そして、シェルスクリプト/Chef/Puppet といった業界標準的に提供されているツール類は、 マシン上のソフトウェアを自動的にインストール/設定するために使用されます 。
ざっくりいうとVagrantとは仮想環境の設定・操作用統一ツールです。
いままで開発用の仮想環境を作成しようとすると仮想環境用ツール (VirtualBoxやVMWareなど)を使い、OSのイメージをダウンロード・インストール、初期設定などをいろいろ行って初めて仮想環境が構築できます。
しかしそれをチームメンバーそれぞれが行うことでOSのバージョンの違い、仮想環境ツールの違い、仮想環境ツールの設定の違いなどが発生してしまい、みんなが同じ環境で開発を行うことが難しく手間がかかる作業でした。
Vagrantを使う場合 、それらの仮想環境の設定がすべてVagrantfileという設定ファイルに記述され、 仮想環境の操作もvagrantというコマンドラインツール経由で行います。
仮想環境の設定がVagrantfile経由で行われるため、チームメンバーに同じ環境を構築してほしい場合はそのVagrantfileを使ってその人も同じようにvagrant経由で仮想環境を起動すると同一の環境が簡単に手に入るということです。
他にもVagrantではホストマシンとゲストマシン (Vagrantで作成した仮想環境)の指定したディレクトリを同期する機能がデフォルトで簡単に利用できるため、いままで仮想マシンで開発を行っていた ローカルでファイル編集 => 仮想環境にアップ => 仮想環境内で実行 というフローが ローカルでファイル編集=> 仮想環境で実行 という風に簡略化できます。
Vagrantはローカルマシンに構築する仮想環境の他にもAWSのEC2をvagrantコマンドでコントロールすることも可能ですが、個人的には本番環境と開発環境を同じツールで簡単に操作できるのは少し怖いところもあるので、Vagrantはあくまで開発環境の構築&操作ツールとして利用しています。
chefとは
chefについて、@ITの記事では以下のように説明されています。
「Chef」とは、システム構築を自動化するためのソフトウェアツールあるいは枠組み(フレームワーク)の一種。システムの各種設定を変更したり、アプリケーションやミドルウェア、コンポーネントなどのソフトウェアをインストールしたりする作業を自動化できる。WindowsやLinux、Mac OS Xなど複数のプラットフォームに対応している。
chefはざっくりいうとサーバー設定ツールです。
例えばPHPをつかったWebアプリケーションを動作させるためのサーバーを設定しよう思うとWebサーバーとしてApacheをインストールし、PHPをインストールし、DBを使うならMySQLなどをインストールします。もちろんサーバーの構築はこれだけでは終わらず、セキュリティ用にネットワークの設定をいれたり、ツールをインストールしたり、インストールしたツール群の設定などもそれぞれ行う必要があります。
サーバーの設定を行うのが一台だけならまだ良いのですが、冗長化するために複数台サーバーを立てる場合に すべて手作業で行うのは効率が悪いのでそれらの設定作業をプログラミングで自動化しようというのがchefの役割です。
他にもchefは冪等性(何回実行しても結果は同じ)という特徴をもっているため、すでに稼働しているサーバーに対して実行しても環境を壊すことなくchefで記述した環境に収束させます。
そのためchefのコードを見るだけでサーバーの現在の状態が把握できるドキュメントの役割も果たしてくれます。
Dockerとは
Dockerについて、またまた@ITさんの記事では以下のように説明しています。
「Docker」とは、Docker社(旧dotCloud)が開発するオープンソースのコンテナー管理ソフトウェアの1つです。
上記の説明で記載している通りDockerはコンテナー管理のツールであるため、コンテナー型仮想化技術と強く紐付いています。
仮想化にはホスト型、 ハイパーバイザー型、コンテナー型と幾つか種類があり、上記で説明したVagrantで使用しているVirtualBoxが行う仮想化はホスト型、AWSの EC2やさくらクラウドなどクラウドサービスなどで使われる仮想化はハイパーバイザー型の仮想化です。
それぞれの仮想化技術の詳細については長くなるため省略しますが、 コンテナー型と他の仮想化との違いはその軽さです。
他の仮想化技術は仮想的な「物理マシン」を作りだすというアプローチのため、物理マシンの上の仮想空間に独自のLinuxカーネルやOSなどをインストールしますが、コンテナー型では物理マシンのLinuxカーネルは共有して、別々のユーザー空間を作り出すというアプローチを取っています。
Dockerではコンテナー型で作られた仮想環境をDockerfileという設定ツールを使って設定を入れ込んでいきます。 それによってそのDockerfileを使えば同じコンテナー型の仮想環境が作成できます。
それぞれのツールの関係
vagrant & chef
vagrantとchefはとくに一緒に語れることが多いので混同されやすいのですが、vagrantは仮想環境の操作・設定ツールでchefはサーバー設定ツールです。 よく使われるパターンだとvagrantで作った仮想環境をWebアプリケーションが動作できるようにするためにchefで設定を入れる、のような感じです。
「Vagrantfileでもサーバーの設定を入れる」と思ってしまうときがあるのですが、Vagrantfileではサーバーの設定を入れるためにシェルスクリプトを使おうがchefを使おうがどちらでも構いません。 Vagrantfileで行うのは「このコマンドを使ってサーバーの設定をおこなう」という入り口を提供してくれているだけで、その設定ツールの選択肢のひとつとしてchefがあるという関係です。
vagrant & Docker
まったく関係ありません。
vagrantは仮想ハイパーバイザー型やホスト型仮想化ツールを通して仮想環境を操作するのに対して、Dockerはコンテナー型の仮想環境を操作します。
ユースケースが似ているため混同されるかもしれないですが、お互い対象としている仮想化技術が違うため組み合わせて使うことはまずないと思われます。
chef & Docker
組み合わせて使うことも可能ですが、あまりメリットがないかもしれません。
chefはサーバーを構築する際にその対象となる環境でrubyとchefが実行できればサーバー設定を行うことが可能なので、Dockerで作ったコンテナー型仮想環境でも設定の自動化のために使用することは可能です。
ただDocker自体にDockerfileというサーバーの設定を記述して自動化する機能を提供しているため、それをchefに置き換える必要があまりありません(既存のchefの資産が生かせるというメリットはありますが・・)
さらにDockerで作成するコンテナー型仮想化技術はプロセス内でその仮想環境が構築され、そのプロセスを消すと仮想環境自体もなくなるため基本的には毎回一からDockerfileを使って仮想環境を構築することになります。 chefのメリットのひとつに冪等性がありますが、それが有効なのはすでにchefによってサーバーの設定が入っているところに何度も実行しても環境が同じであることを保証したいからです。 Dockerのように毎回一から作成する環境の場合は何度も実行することがそもそもないので冪等性は不要になります。
なのでDockerを使うのであればサーバーの設定もchefを使わずDockerで完結させる方が適切でしょう。
終わりに
Vagrant, chef, Dockerがまだわからない人にざっくりとした説明ができるようにこの記事を書いてみました。
本当はDockerの使いどころなども必要なので書きたいと思ったのですが、体力が尽きたのとそんな記事はいくらでもあるのと、何よりまだ自分が仕事で使っていないという理由で書くのは省略しちゃいました ><
Web開発の現場だとほとんどVagrantとchefに類似するサーバー設定ツール(Puppet, Ansibleなど)は使うのはもうスタンダードになっていて、Dockerはまだこれからだが確実にスタンダードになる技術っていう感じだと思います。
Dockerを使う場合はアプリケーションの設計自体も変更が必要になる大きな変化なので、しっかりとキャッチアップしておきたいな。
参考URL
Vagrantを使う理由 | Vagrant日本語ドキュメント
Windows Insider用語解説:Windows/Linuxで使えるシステム構築・設定の自動化ツール「Chef」とは? - @IT
kumadori というgemを公開しました。
はじめに
仕事でRubyを使うようになってから5ヶ月程、前々から自分のgemをつくって公開したいと考えていました。
非常に小さい単機能のgemですが、今回はじめて自作gemを作って公開できたのでそのご報告をしたいと思います。
kumadoriとはなにか?
kumadoriはRubyでデコレーターパターンをシンプルに実装するためのgemになります。
デコレーターパターンとはプログラミングのデザインパターンのひとつで、既存のオブジェクトにデコレーターオブジェクトをラップすることで柔軟にオブジェクトの機能拡張を行なうことが出来る手法です。
kumadoriという名前ですが、歌舞伎の化粧のことを隈取(くまどり)ということから命名しました。
個人的にソフトウェアの名前に日本語が使われているのが結構好きなので、デコレーターを表すなんか良い感じの日本語ないかな〜って考えた所、 デコレーター -> 装飾者 -> おしゃれする -> 化粧 -> 歌舞伎の化粧を「隈取」という無理な連想ゲームでkumadoriという名前にしました。
使い方
kumadoriはデコレーションの対象となるインスタンスのClass名によって自動的に使用するDecoratorクラスを決定します。
例えばUserクラスのインスタンスをデコレーションするのはUserDecoratorクラス、Animalクラスのインスタンスのデコレーションを行なうのはAnimalDecoratorクラスのような感じになります。 そのため、使用するDecoratorクラスを予めKumadori::BaseDecoratorクラスを継承して定義して下さい。
インスタンスをデコレーションするときはKumadori.decorate(instance)メソッドを使います。
以下に参考コードを記載します。
# # Basic ruby class. # class User attr_accesstor :first_name, :last_name def initialize(first_name, last_name) self.first_name = first_name self.last_name = last_name end end # # Decorator class for User instance. # class UserDecorator < Kumadori::BaseDecorator def full_name "#{self.last_name} #{self.first_name}" end end user = User.new('Yuji', 'Arakaki') # user instance decorated by UserDecorator decorated_user = Kumadori.decorate(user) decorated_user.full_name # => "Arakaki Yuji"
もしデコレーションするインスタンスに該当するDecoratorクラスがない場合、Kumadori::BaseDecoratorクラスによってデコレーションされます。
そのため、もしどのオブジェクトにも共通して追加させたいメソッドなどがあれば、Kumadori::BaseDecoratorクラスをOverrideして定義してください。
class Animal end module Kumadori class BaseDecorator < ::SimpleDelegator def live? true end end end animal = Animal.new # Because of AnimalDecorator class is not defiend, # it is decorated by Kumadori::BaseDecorator decorated_animal = Kumadori.decorate(animal) decorated_animal.live? # => true
配列などのCollection内に入っている各要素をすべてデコレーションしたい場合はKumadori.collection_decorate(collection)を使ってください。
Collection内に入っている要素をすべてデコレーションした新しいCollectionを返します。
members = [] members << User.new('Kanoko', 'Higa') members << User.new('Ai', 'Kawasaki') members << User.new('Takeo', 'Kikuchi') decorated_members = Kumadori.collection_decorate(members) decorated_members.map{ |user| user.full_name} # => ["Higa Kanoko", "Kawasaki Ai", "Kikuchi Takeo"]
このGemの使いドコロ
僕がこのGemを作ったのは、Railsでモデルに格納されているデータをフォーマットして表示する処理を定義するときに、表示に関する処理はドメインロジックではないのであまりモデル内には記載したくないが記述する良い場所がないのでDecoratorというレイヤーを作りたいと考えたからです。
Decoratorというレイヤーを入れることで、モデル記述していた表示に関するメソッドが整理できるのでより見通しが良くなり、変更もDocoratorを変更すればモデル自体を変更せずに済むのでより柔軟性が高まります。
RailsにDocoratorというレイヤーを導入するGemはactive_decoratorやdraperという有名どころのgemもあるのですが、どちらもRailsのプラグインになっているためRails依存しない形式でデコレーターを簡単に使えるgemにすればすぐに実装出来るしより使いやすいと思ってkumadoriを作ってみました。(まあ、あとで調べるとそういうgemは山ほどあったのですが・・)
おわりに
超絶シンプルなgemですが、人生初のOSSを公開したということでほんの少しテンションが上がっています^^
OSSということでコミットメッセージやREADMEも英語で書いたり、テストどれくらい書けば良いか考えたりして色々楽しかったです。
これからも小さいgemでも大きいWebサービスでも良いので色々開発して定期的にアウトプット出来るように頑張りますd^^
MacBookProのメモリを増設
メモリ増設開始!
フレームワークなくとも秩序は成り立つ ~ フロントエンドJavaScript ~
はじめに
いま開発しているWebアプリケーションでフロントエンドのJSを書こうとしているときに、以前の開発でAngularJSを使用していた経験があることもあり、自然にどのフレームワークを採用しようかと色々物色しておりました。
AngularJSの他にもEmberやKnockout, Backbornなど調査してみたんですが、どのフレームワークも「SPA」(シングルページアプリケーション)を作ることを前提に作られているようでした。
自分が開発中のアプリケーションはSPAではないし、AngularJSを採用したプロジェクトの時もバックエンドで採用しているフレームワークとの相性とかで結構苦労したのでどうしようかな〜と考えて、っていうかフロントエンドMV*フレームワークいる?って所から考えなおしてみました。
フロントエンドMV*フレームワークを使いたい理由
もちろんチームやプロダクトの性質によると思いますが、僕が考えた上では
のような感じかな〜と思いました。
適当にJSでゴリゴリ書いちゃうとどのファイルに何があるかがわからずに見通しが悪くなるし、JSはUIをいじることに使われる特性上デザインの変更に対して対応出来ないといけないので変更ができるだけ簡単に行えるようにしておかないといけない。 さらにあわよくばデータバインディングやフィルタやらの力を借りて効率よく開発したいって感じです。
忘れちゃならないトレードオフ
ただ、フレームワークの採用にはもちろん良いことだけでなく、以下のようなトレードオフも存在します。
- 学習コスト
- フレームワークへの依存
学習コスト
そのフレームワークを使いこなせるようになるとどんどん開発効率が上がっていきます。
ただそのフレームワークを使いこなすまでにはそのフレームワーク独特の概念やAPIを学習するのにそれなりの時間がかかり、さらにそれを使いこなすためにも時間がかかります。
さらにフレームワークを使うと必ずそのフレームワーク独特の問題などぶつかることも多く、それを解決するのに余計な時間がかかってしまう可能性も考慮する必要があります。
フレームワークへの依存
フレームワークはあらゆるアプリケーションに使えるわけではなく、そのフレームワークが想定しているユースケースがあります。 そのユースケースに沿って使えば効率良く開発が行えますが、そこから外れたことをやろうとすると余計なHackが必要になってしまいます。
そのためフレームワークの選定を間違えると余計なコストがかかったり、わざわざフレームワークに合わせたアプリケーションになってしまうという恐れもあります。
必要ないなら使わない方が良い
今回私が行っているWebアプリケーション開発の状況は以下のような感じでした。
- このアプリケーション開発以外のプロジェクトもやってる
- アプリケーションはSPAじゃない
- インフラ、バックエンド、フロントエンド、ネイティブアプリまで全てを担当するためフロントエンドの専任エンジニアはいない
ざっくり言うと、「やることはたくさんあるからフレームワークの選定ミスで余計なコストを掛けたくない!」っていう状況です。
その状況のなかで考えたときに
- フレームワークの機能での効率化は今回の場合学習コスト + 相性の悪さで多分チャラになる
- でも見境なくJS書いてカオスにもしたくない。
- 自分である程度JSの構造化をすればいいんじゃね?
っていうことになりました。
おれおれのフロントエンドJS設計
フロントエンドJavaScriptで書くコードをざっくり以下の様に分離します。
- Model: ビジネスロジックの管理
- Template: HTMLの生成
- ViewModel: イベントハンドリングやデータフロー
ディレクトリ構造は以下のような感じになります。
├── javascripts │ ├── models │ │ └── user.js │ ├── templates │ │ └── errors_message_template.js │ │ └── day_options_template.js │ └── viewmodels │ ├── profile_viewmodel.js │ └── top_viewmodel.js
Model (ビジネスロジック)
MVCフレームワークでもモデルと呼ばれ 、アプリケーション特有のロジックを入れるレイヤーです。 たとえばユーザープロフィールの登録ページを実装する場合、Userというモデルオブジェクトを作り、そこにバリデーションの条件やバックエンドのAPIとの通信などのコードを書きます。
ここにあるコードはデザインとの結びつきがなく、どのページでも使いまわせるようにしておきます。
参考コードとして下記のUserモデルをのせておきます。
function User(name, email, job) { var self = this; self.name = name: self.email = email; self.job = job self.nameIsBlank = function(){ self.name.length === 0; } }
Template(HTMLの生成)
入力フォームをチェックしてエラーメッセージを表示したり、Ajaxやユーザーのアクションに反応して変化するHTMLを生成するコードです。 例えば日付に関する入力フォームで年、月、日のセレクトボックスがある場合、年、月が変更されると日のセレクトボックスのoptionタグはその月にある日の数に合わせて変化しないといけません(8月なら31個、2月なら28個のように)。
それを実現するために年と月の値からoptionタグを必要な数だけ生成するコードを書きます。
ここは同じようなHTMLが必要な箇所があれば上手く使いまわせるように引数によって動的な値を管理します。
以下に年と月を引数に受け取って必要なoptionタグを生成するTemplateのコードを記載します。
/* * 引数のyearとmonthからその月の開始日と終了日までのoptionタグを生成する。 * selectedDayに指定した日付のoptionタグにはselected="selected"の属性を設定する。 * 例: * <option value="1">1</option> * <option value="2">2</option> * <option value="3" selected="selected">3</option> * <option value="4">4</option> * <option value="5">5</option> * . * . * <option value="31">31</option> * */ function DayOptionsTemplate(year, month, selectedDay){ var self = this; var jsMonth = month - 1; var monthFirstDate = new Date(year, jsMonth, 1); var monthLastDate = new Date(year, jsMonth + 1, 0); self.template = generateTemplate(monthFirstDate.getDate(), monthLastDate.getDate(), selectedDay); function generateTemplate(firstday, lastDay, selectedDay){ function a_day_option_template(day, selected){ var attr = ''; if (selected === true){ attr += ' selected="selected"'; } var template = '<option value="' + String(day) + '"' + attr + '>' + String(day) + '</option>'; return template; } var template = ''; for(var i=firstday; i<=lastDay; i++) { var selected = i === selectedDay ? true : false; template += a_day_option_template(i, selected); } return template; } }
ViewModel(イベントハンドリング・データフロー)
HTMLの要素に対してイベントをつけたり、モデルの変更をキャッチした結果生成したHTMLを実際のDOMに差し込んだりする所です。
ここの部分が一番DOMに結びつきやすく、使い回しも効きにくいため、Webアプリケーションのページ毎に作成するイメージです。
DOMと結びついた面倒な部分をここに押し込めることで他のModelやTemplateの独立性を高めています。
以下にプロフィール登録ページのViewModelのサンプルコードを記載します。
ProfileViewModel = function(){ var self = this; $(document).ready(function(){ // イベントハンドリング $('form[name="profile" button.submit]').click(function(){ // データフロー // Formの値をモデルに与える var name = $('input[name="user[name]"').val(); var email = $('input[name="user[email]"').val(); var job = $('input[name="user[job]"').val(); var user = new User(name, email, job); if (user.nameIsBlank === true) alert('名前を入力して下さい'); return false end }); // イベントハンドリング $('select[name='month']').change(function(){ // データフロー // 選択されている値を元にテンプレートを作成する var year = $('select[name='year']').val(); var month = $('select[name='month']').val(); var day = $('select[name='day']').val(); var dayOptions = DayOptionsTemplate(year, month, day); // テンプレートの差し込み if ($("select[name='day']").children().length > 0){ $("select[name='day']").empty(); } $("select[name='day']").prepend(dayOptions.template); }) }); }
このViewModelを使いたいページで実行すれば、必要な処理がすべて用意されるという感じです。
ViewModelの実行はscriptタグで行っても良いですが、dispatcherを作ってURLで必要なVIewModelをセットすると より見通しさらによくなります。
dispatcher("^/profile", function(){ ProfileViewModel(); }); function dispatcher(path, func){ dispatcher.path_func = dispatcher.path_func = dispatcher.path_func || [] if (func) return dispatcher.path_func.push([path, func]); for(var i = 0, l = dispatcher.path_func.length; i<l; ++i ){ var func = dispatcher.path_func[i]; var match = path.match(func[0]); match && func[1](match) }; }; dispatcher(location.pathname);
終わりに
とりあえずJSがカオスにならずに秩序をもって構造化でき、ある程度使い回しが可能なコードが作れるような設計を、フレームワークなしで実現できるように取り組んでみました。
いままさに開発中のアプリケーションでは結構気持ちよくこの設計が使えていますが、まだまだ解決したい問題は多々あります。
たとえば分離したJSファイルの依存性を解決したり、Templateが純粋なJSなのでデザイナーが触りづらいなど色々あります。
その解決方法も目処は立っている(依存性解決はWebpackを使ったり、Templateもそれを解決してくれそうなライブラリがある)のですが、いまは自分達のプロジェクトでは回っているので問題が顕在化しそうになってから取り組めば良いかな〜という感じです。
必要なコストが払えるのであればフレームワークを導入するのも良いですが、 自分達のプロジェクトに最適化した設計を行なうことが出来るのであればその方が効率は上がるし、プログラミングをしていてもかなり楽しくなるのでおすすめします^^
APIファーストでバックエンドとフロントエンドを別々に開発する時にハマるクロスドメインアクセス
いま個人で開発を行っているWebサービスがありまして、そこではバックエンドをRubyonRails、フロントエンドをClojure & ClojureScriptで開発し、APIでお互いやりとりするように設計しています。(その設計した理由は色々ありますが、単純に好きな2つの言語を使いたかったのが大きな理由の一つですw)
おそらくこれからのアプリケーション開発において、このようにフロントエンドとバックエンドをサーバーも言語も分けて設計・開発することが多くなると思いますので、最近ハマった問題をシェアしておきます。
同一生成元ポリシー(Same Origin Policy)
そのハマった問題というのが同一生成元ポリシーによるAjaxの規制です。
同一生成元ポリシーとは、ドメインAに配置されているHTMLファイルから別ドメインBのサーバーのAPIにAjaxで通信することが出来ないという制約です。
この制約があることで、例えば開発中にフロントエンドをlocalhost:8000で起動し、バックエンドをlocalhost:8001で起動してAjaxでAPI通信しようとするとエラーが発生してしまいます。
では、フロントエンドから別サーバーのAPIと通信するにはどうすればいいのか・・
CORS(Cross-Origin Resource Sharing)
実はこういう希望に対処するためにCross-Origin Resource Sharingという、XMLHttpRequest(Ajaxのことです)でクロスドメインアクセスを実現する仕様をW3C勧告で各ブラウザが実装しています。
CORSではクロスドメインアクセスを行うクライアント側とアクセスされるサーバー側の振る舞いが仕様で規定されています。
その概要はブラウザとサーバーがHTTPヘッダを使ってアクセス制御に関する情報をやりとりしてアクセスを行うものです。
CORSを実際に行う場合にクライアント側のJSで特別な制御を行う必要はあまりないのですが、サーバー側で以下のようなアクセス制限に関するルールを設定する必要があります。
では、これからどのようにCORSを行っているかを説明していきます。
CORSの動作 - クライアント側
クライアント側がクロスドメインアクセスを行うときに行う通信手段には2パターンあります。
以下に記載する条件にすべて該当する場合はpreflightリクエストを送る必要がないと判断して、直接サーバーにリクエストを送信します。逆にいうと、この条件に合わなければ必ずpreflightリクエストを送るということです。(これらの判断はすべてブラウザが行ってくれるのでクライアント側で特別にコードを書く必要はありません。)
preflightリクエストを送信しない条件
- HTTPメソッドがGET, POST, HEADのどれか
- HTTPヘッダにAccept, Accept-Language, Content-Language, Content-Type以外のフィールドが含まれていない
- Content-Typeの値がapplication/x-www-form-urlencoded, multipart/form-data, text/plainのいずれか
上記の条件をみて頂けると気づくと思いますが、APIでデータをやりとりするときはjsonやxmlをフォーマットとして使うことが多いので、大半はまず間違いなくpreflightリクエストが送られるということです。^^;
CORSの動作 - サーバー側
Originのチェック
サーバー側では、まず受け取ったリクエストがクロスドメインから受け取ったリクエストか判断するためにリクエストヘッダに含まれるOriginフィールドの値が入っているかチェックします。
Originフィールドに値が入っていれば、そのドメインがCORSを許可したドメインかをチェックして受け入れるか否かを判断します。
preflightリクエストかどうかを判断する
次にサーバーはこのリクエストが通常のリクエストかpreflightリクエストかを判断しないといけません。実際には以下の項目をチェックしてpreflightかどうかを判断します。
ちなみに、僕はクロスドメインでエラーが発生したリクエストを調査しているときに、POSTで送ったはずのHTTPメソッドがOPTIONSになっていて思わずライブラリのバグかなと中のコードを30分程探ってしまいました(- -;)
アクセス許可メソッドのチェック
続いて、HTTPメソッドがアクセスを許可しているものかどうかをチェックします。
preflightリクエストが来ていた場合、Access-Control-Request-Methodに本来送るはずのHTTPメソッドがここに記載されているので、この中身もみてアクセスを許可するかどうかを判断します。
レスポンスヘッダの作成
Access-Control-Allow-Origin
CORSが成立して、リクエストに対してレスポンスを返す時にレスポンスヘッダのAccess-Control-Allow-Originフィールドにクロスドメインアクセスが許可されるドメイン名を付加します。
もしAccess-Control-Allow-Originのフィールドがない場合はブラウザはクロスドメインアクセスに失敗したと判断してエラーを発生させます。
リクエストがpreflightリクエストだった場合は他にもレスポンスヘッダを追加します。
Access-Control-Allow-Methods
preflight後の実際のリクエストで利用を許可するHTTPメソッドの情報を付加します。
Access-Control-Allow-Headers
preflights後の実際のリクエストで利用を許可するリクエストヘッダの情報を付加します。
Access-Control-Max-Age
preflightの結果をキャッシュする時間を付加します。毎回毎回クロスドメインアクセスを行うたびにpreflightリクエストを送ると送受信コストが無駄にかかってしまうため、こちらで指定した時間はその結果をキャッシュしブラウザ側はpreflightリクエストを送らずに直接リクエストを送信します。単位は秒で指定します。
理屈は分かった。で、どうすればいいんだ!?
上の説明でCORSを行う仕組みはなんとなく把握できたと思うのですが、じゃあ具体的にどういう対応すればいいのでしょうか?
自分の場合はバックエンド側にはRubyonRailsを使っており、幸いCORS対応を行うためのgemでrack-corsというのがすでにあるのでそちらを使いました。
rack-corsのREADMEを見れば良いのですが、Gemfileにrack-corsを追記してbundle installしたあとに、config/application.rbに以下のように記載します。
module YourApp class Application < Rails::Application # ... config.middleware.insert_before 0, "Rack::Cors" do allow do origins 'your_app_domain' resource '*', :headers => :any, :methods => [:get, :post, :put, :delete, :options] end end end end
自己紹介です。はじめまして、arakajiです。
ブログ開設しました。arakajiです。
私は現在24歳で、沖縄在住のアマチュアプログラマーです。プログラミングをはじめて約10ヶ月になり、触っている言語はPythonとRubyなんですがメインでRubyを使ってます。
アルゴリズムを知るためにJavaやJavaScriptのコードはたまに読むんだけど、ruby, pythonで
入ってるから色んな宣言が多くてびっくりしたね!でも、近いうちそういう言語も覚えないといけないかなー。
使っているOSはLinuxのUbuntuです。最初の半年以上はwindows7でやってたんだけど、rubyで色々やろうとすると引っかかる引っかかる(TーT)それで一念発起して恐る恐るUbuntuを入れてみたら・・・・なんということでしょう!
rubyの扱いがぐっ!!と楽になるではありませんか。
gemも簡単にインストールできるし、文字コードでの苦労もなくなるし。rubyでプログラミングするならやっぱLinux!っていうネットの意見を聞いて正解でした。ただいつかMacも使ってみて、なぜAppleファンがあんなに熱狂的なのか感じたいっていう希望もあります。
さて、このブログですが、基本プログラミングや覚えた技術について書くつもりです。
でも、他にも書きたいことがあったら書くつもりです。
良いプログラマーはブログをしている人が多いということで始めたので、自分のために書くのがメイン!それプラス、ここでの発信がすこしでも素晴らしきハッカー文化に貢献できたら幸いです。
なので、出来る範囲で更新していくので気軽に見にきてくださいねv^_^
P.S
いまプロプログラマーになろうと就活中なので、興味をもって頂けたら連絡頂けると嬉しいです。
興味をもって頂けなくても、求人に応募してくるかもしれないですし、なにかの機会にお会いことがあれば、よろしくおねがいします。