生のHTMLを書くことが辛くなってきた。
自分はWebアプリケーションエンジニアで、 1年程前まではサーバーサイドをPHPで書いていたのでその延長でHTMLを書くことには特に何も思うことはありませんでした。
ただこの一年ぐらいはサーバーサイドにRubyonRailsを採用してSlimというテンプレートエンジンを使っています。 SlimというのはHTMLをもっと簡易的にかけるツールで、例えば以下のようなHTMLを
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Sample File</title> </head> <body class="sample"> <div id="contents"> <h1>Sample File</h1> <img alt="yterajima" src="http://www.e2esound.com/images/yterajima.jpg" /> <p>テキストテキストテキスト</p> <p>テキストテキストテキスト テキストテキストテキスト</p> </div> </body> </html>
以下のように簡易記法でかけます。
doctype html html head meta charset="utf-8" title Sample File body.sample #contents h1 Sample File img src="http://www.e2esound.com/images/yterajima.jpg" alt="yterajima" p テキストテキストテキスト p | テキストテキストテキスト テキストテキストテキスト
もし興味があれば、slimの詳しい説明は公式サイトや速習テンプレートSlim(HTML作成編)を見ていただけるとよくわかるので参考にしてみてください。
一度Slimのようなテンプレートエンジンを使うと、書くコードが格段に減りますし、コードが減るので効率、可読性、メンテナンス性も格段に上がります。 その恩恵を受けると、生でHTMLが辛くなってくるんですよねーー。
フロントエンド開発にRailsアプリでもnodeのエコシステムを使うという選択
はじめに
RailsではデファルトでAsset Pipelineという機能により、SassやaltJs(CoffeeScriptやTypeScript)のコンパイルやファイルの結合、圧縮を行ってくれる機能があります。 なので通常はバックエンドをRailsで開発する場合はフロントエンドもRailsに沿って開発を行います。
最近自分がメイン開発者(というかほぼ一人?)として新Webサービス開発プロジェクトが始まり、バックエンドのフレームワークはRailsを使うことにしたのですが、様々な検討の結果フロントエンドはRailsのエコシステムではなく、nodeのエコシステムを使うことを選択しました。
振り返ったときにこの選択が正解だったのかどうかを確認するためにも、ここでなぜこの選択をおこなったのかをまとめておきたいと思います。
依存性の管理にnpmを使いたい
フロントエンド開発を生産的に行うに、当然様々なライブラリを扱います。
そのライブラリを管理するのに一番ベタな方法はライブラリのソースをダウンロードしてライブラリ用ディレクトリに設置することです。
そこから少し進化したのが、Bowerというパッケージ管理ツールのbower.jsonにアプリが依存しているライブラリを明記してインストールするようになりました。
ただ最近ではnodeが提供している標準のパッケージ管理システム npmを、そのままフロントエンドJavaScriptの依存性管理にも使えるようにするbrowserifyというツールも使用されるようになっています。 このbrowserifyが優れもので、サーバサイドのnodeでライブラリを使用するときに行うrequire('module名')をフロントエンドJavaScriptの世界でも使えるようにしてくれます。
var react = require('react')
あくまで私の観測範囲ですが、フロントエンドに関する記事やライブラリのREADMEなどを見ているとフロントエンドJavaScriptの世界での依存管理でもnpmを使うことが前提となっていく感じがしていて、それが出来ないがために何かを行うときに無駄にハマってしまう予感がしています。
他チーム、他プロジェクトへノウハウの共有がしやすい
フロントエンド開発は高度化、複雑化しており、上記にあげた依存性の管理の他にもSassやAltJSのコンパイルや圧縮、結合、ライブラリの選定や設計など様々な問題を解決しなければなりません。 これらをRailsエコシステムにのった方法で解決しても、その知識は他のフレームワークを利用したプロジェクトやチームと共有できません。
うちの会社で行われる開発でRailsを選択するのは私がメンバーに入るときぐらいで、他の場合はCakePHPやほとんどバックエンドが必要ないWebサイトの制作が大多数を占めています。
自分が学んだ知識を他のチームとお互いに共有したり、また他のチームの開発に入った時に自分がスムーズにヘルプできるようにし、会社全体でのフロントエンド開発の生産性を高めるためにもフロントエンド開発ではnodeのエコシステムに乗るべきだと判断しました。
おわりに
今回のプロジェクトではサービスの企画立案から、開発に関わる技術選択、実際の開発まですべてに関わることができるのですが、あらためて「どの技術を選択するか」を考えることの難しさと面白さを感じました。
良い技術選択を行えば、開発者のモチベーションアップや生産性アップにもつながれば、その逆に陥ることもあります。
今回意識したのは「1年後の当たり前になっているか」ということで、1年後はいま以上にフロントエンド開発のnode前提は加速すると予測し、この選択を行いました。
この選択によって、このブログにフロントエンド界隈のポストが増えてくると思います^^*
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もそれを解決してくれそうなライブラリがある)のですが、いまは自分達のプロジェクトでは回っているので問題が顕在化しそうになってから取り組めば良いかな〜という感じです。
必要なコストが払えるのであればフレームワークを導入するのも良いですが、 自分達のプロジェクトに最適化した設計を行なうことが出来るのであればその方が効率は上がるし、プログラミングをしていてもかなり楽しくなるのでおすすめします^^