MMiyauchi Blog

大注目のフルスタックフレームワーク「Meteor」を試してみた (2)

前回は、Meteorの紹介に終わった。今回は公式チュートリアルを実際にやってみる。

Todoアプリの公式チュートリアルをやってみる

前回記事にて、Todoアプリをたかだか1コマンドで生成し、実行したあとでとてもアレなのだが、フレームワークを理解するために今度は自分でやってみようと思う。最近では、Angular JSが必要な開発に直面していることもあり、チュートリアルはAngular版Todoのものをやってみたいな…と思ったのだが、よくよく見てみるとほとんど普通のAngularっぽい感じだったので、ここは思い切ってMeteorの標準(?)のBlazeの構文でやってみようと思う。

2017年4月14日更新

Blazeの注意点については下記の記事を参照。Meteor 1.4より、BlazeはMeteor公式という立ち位置からは外れている。Blazeは簡素フレームワークのため、現状でもチュートリアルには向いている。

Meteorアプリケーション(Blaze)の運用をしてみてハマったこと – MMiyauchi Blog
http://mmiyauchi.com/?p=1465

基本的には、Meteor 1.2.1の公式チュートリアルを噛み砕いて日本語でやっていくものと思って欲しい。説明用のコードも公式チュートリアルと全く同じである。

アプリケーションの作成

まずはアプリケーションのひな型を作成する。

$ meteor create simple-todos

基本書いてあるまんまだけど、重要なのは、.meteorという隠しディレクトリにMeteorの全てが詰まっているということと、あくまでこの3つのファイルでサーバサイドとフロントサイドの仕組みを作ることができるということ(正確には.htmlと.jsファイルの2つ)。

simple-todos.js       # a JavaScript file loaded on both client and server
simple-todos.html     # an HTML file that defines view templates
simple-todos.css      # a CSS file to define your app's styles
.meteor               # internal Meteor files

あとはアプリケーションを実行するために、該当ディレクトリに移動し、サンプルアプリを立ち上げておく。

$ cd simple-todos
$ meteor

ちなみに、meteorコマンドを打ったのちに、このままターミナルを立ち上げっぱなしを推奨である。なぜなら、コードを変更しても勝手にMeteorサーバが自動的に再起動するからである

テンプレートを作成し、表示する

ひな型アプリを作成したところで、htmlファイルを下記のように変える。

<head>
  <title>Todo List</title>
</head>
 
<body>
  <div class="container">
    <header>
      <h1>Todo List</h1>
    </header>
 
    <ul>
      {{#each tasks}}
        {{> task}}
      {{/each}}
    </ul>
  </div>
</body>
 
<template name="task">
  <li>{{text}}</li>
</template>

次に、.jsファイルも下記のように編集する。

if (Meteor.isClient) {
  // This code only runs on the client
  Template.body.helpers({
    tasks: [
      { text: "This is task 1" },
      { text: "This is task 2" },
      { text: "This is task 3" }
    ]
  });
}

赤くはなっていないだろうが、ブラウザで確認すると、公式にあるようにこんな感じでヴューが生成されていることだろう。

ここでは、正当なHTMLの構造をあまり考えずにいたほうが、分かりやすいかもしれない。simple-todo.jsに書いたテキストを<template>要素内に出力している。nameでtaskとしていて、それを#each tasksで取得して、li内に表示している。Meteorでは<template>要素がMeteorのテンプレートとして解釈される。

次に、CSSを下記のようなCSSで、simple-todos.cssを置き換える。

/* CSS declarations go here */
body {
  font-family: sans-serif;
  background-color: #315481;
  background-image: linear-gradient(to bottom, #315481, #918e82 100%);
  background-attachment: fixed;

  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;

  padding: 0;
  margin: 0;

  font-size: 14px;
}

.container {
  max-width: 600px;
  margin: 0 auto;
  min-height: 100%;
  background: white;
}

header {
  background: #d2edf4;
  background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%);
  padding: 20px 15px 15px 15px;
  position: relative;
}

#login-buttons {
  display: block;
}

h1 {
  font-size: 1.5em;
  margin: 0;
  margin-bottom: 10px;
  display: inline-block;
  margin-right: 1em;
}

form {
  margin-top: 10px;
  margin-bottom: -10px;
  position: relative;
}

.new-task input {
  box-sizing: border-box;
  padding: 10px 0;
  background: transparent;
  border: none;
  width: 100%;
  padding-right: 80px;
  font-size: 1em;
}

.new-task input:focus{
  outline: 0;
}

ul {
  margin: 0;
  padding: 0;
  background: white;
}

.delete {
  float: right;
  font-weight: bold;
  background: none;
  font-size: 1em;
  border: none;
  position: relative;
}

li {
  position: relative;
  list-style: none;
  padding: 15px;
  border-bottom: #eee solid 1px;
}

li .text {
  margin-left: 10px;
}

li.checked {
  color: #888;
}

li.checked .text {
  text-decoration: line-through;
}

li.private {
  background: #eee;
  border-color: #ddd;
}

header .hide-completed {
  float: right;
}

.toggle-private {
  margin-left: 5px;
}

@media (max-width: 600px) {
  li {
    padding: 12px 15px;
  }

  .search {
    width: 150px;
    clear: both;
  }

  .new-task input {
    padding-bottom: 5px;
  }
}

MongoDB(minimongo)と接続する

タスク保存に使うデータベースとして、minimongoをインストールする(というか、最初から入ってるものを使うみたいな感じなので立ち上げる?のほうが正しいか。パッケージの追加コマンドは$meteor add package-name)。

 $ meteor mongo

minimongoと接続するには、 new Mongo.Collection(“コレクション名”)みたいにする。

Tasks = new Mongo.Collection("tasks");
 
if (Meteor.isClient) {
  // This code only runs on the client
  Template.body.helpers({
    tasks: function () {
      return Tasks.find({});
    }
  });
}

動作確認は、下記のようにコードを変えておこなう。動作確認なので、3行目は終わったらコメントアウトか削除する。

Tasks = new Mongo.Collection("tasks");

db.tasks.insert({ text: "Hello world!", createdAt: new Date() });

if (Meteor.isClient) {
  // This code only runs on the client
  Template.body.helpers({
    tasks: function () {
      return Tasks.find({});
    }
  });
}

フォームからタスクを追加する

simple-todos.htmlにフォームを作成

  <div class="container">
    <header>
      <h1>Todo List</h1>
 
      <form class="new-task">
        <input type="text" name="text" placeholder="Type to add new tasks" />
      </form>
    </header>
 
    <ul>

フォームから送られてきた文字列を処理するコードがこちら

      return Tasks.find({});
    }
  });
 
  Template.body.events({
    "submit .new-task": function (event) {
      // Prevent default browser form submit
      event.preventDefault();
 
      // Get value from form element
      var text = event.target.text.value;
 
      // Insert a task into the collection
      Tasks.insert({
        text: text,
        createdAt: new Date() // current time
      });
 
      // Clear form
      event.target.text.value = "";
    }
  });
}

まさにコメントのまんまで特に説明が必要はないとは思うが、一応流れを日本語にしてみると、event.preventDefault()でsubmitイベントをキャンセル、キャンセルしたのち変数textにフォームから送信されたテキストを格納、Task.insertによりminiMongoにデータを格納している。最後にフォームの内容を消して、もとの入力値がない状態に戻して終わりだ。要は、submitがそのままキャンセルされないでいると、画面リロードが発生してしまうので、あえてキャンセルしているということだ。

Todoリストのチェック機能と削除の実装

テンプレートエンジンを利用して、チェックボックスをチェックしているときの表示の変更と、削除ボタンを押したときのタスクの削除機能をCSSのclassで設定。jQueryのようにCSSのclassの有無でイベントをハンドリングしている。

</body>
 
<template name="task">
  <li class="{{#if checked}}checked{{/if}}">
    <button class="delete">&times;</button>
 
    <input type="checkbox" checked="{{checked}}" class="toggle-checked" />
 
    <span class="text">{{text}}</span>
  </li>
</template>

今度は、テンプレート側で設定したイベントの処理内容をsimple-todo.jsに書く。CSSの特定クラスの付与の有無により、miniMongoのコレクション「task」に保存されている該当するタスクについて処理を行っている。

      event.target.text.value = "";
    }
  });
 
  Template.task.events({
    "click .toggle-checked": function () {
      // Set the checked property to the opposite of its current value
      Tasks.update(this._id, {
        $set: {checked: ! this.checked}
      });
    },
    "click .delete": function () {
      Tasks.remove(this._id);
    }
  });
}

アプリケーションをmeteor.comにデプロイしてインターネット上に公開する

ターミナルで、meteor deployコマンドを実行する。下記my_app_nameの部分はURLで認識できる文字列で適宜変更して実行する。たしか、meteor.comのユーザ登録が最初は必要だと思ったが、それだけで無料でアプリケーションをホスティングできる。

$ meteor deploy my_app_name.meteor.com

(※meteor.comへの無料デプロイは、2016年3月25日で終了した。今後はGalaxyへ有償でのデプロイに対応)

チュートリアルの半分(Webアプリケーションの作成・デプロイまで終了)

これでMeteor公式チュートリアルの、Todoアプリ作成・デプロイのWebアプリケーション編は終了。この先、公式チュートリアルではiOSアプリの作成と色々進んでいくが、とりあえず今回はここまで。

Meteorについて、総評

とりあえず、主にWebアプリケーションに限った話について、良い点、悪い点をさっとまとめてみる。

Pros

  • Meteorの開発環境の構築が1コマンドで楽
  • 標準でリアクティブプログラミングの概念
  • フルスタックのフレームワークだが、JavaScriptがある程度分かっていれば理解はしやすい
  • 生産性が高そう

Cons

  • Meteor本来の挙動を実行するためのデータベースが、現状だとminimongo固定(2016年7月19日訂正  本記事の最下部にリレーショナルDBでリアクティブ動作をするものを追記。しかしながら、Meteor本来の推奨構成はMongoDBとminimongoである)
  • リアクティブ動作のコアがそもそもminimongoなので、RDBのサポートについては今後のMeteor開発チームのSQLサポートの動向によって変わってくる(いわゆる、RDBを使ったしっかりしたアプリケーションには現状向かない)
  • プロダクションで、Meteorアプリケーションを分散させるとかどうするのかが疑問
  • 製品レベルでの成功例が少ない。運用ノウハウの情報が少ない

こんな感じである。総評的には、プロダクションレベルには当てられないが、それでも作る楽しさ、新しい次世代のWebアプリケーションフレームを学ぶ、という観点だとすごく良いフレームワークだと思う。

フルスタックのWebアプリケーションフレームワークのRailsだと環境構築で、四苦八苦する人も多いと思うが、Meteorはほとんど間違えようのないレベルにまで昇華されている(LinuxやMacは1コマンド)。リアクティブプログラミングで、フルスタックという位置付けでメジャーなものだとMeteorが筆頭だと思うし、これも良い。また、フルスタックではあるものの、JavaScriptが分かればそんなに理解に苦労はしなかった。Webアプリケーションを作る分には生産性は高そう、高そうというのはチュートリアルレベルのコードの段階なのであくまで予想だ。また、そもそも論として、Meteorは単にWebアプリケーションの分野にとどまらないことを考えると、かなり高次元なフレームワークと思える。

一方で、Meteorの欠点に目を当てると、製品レベルでの運用のノウハウ欠如に集約されている。例えば、Meteorの推奨された構成を実現するために、クライアントサイドではminimonogoというデータベースを使用している。これはMeteorの構造を実現する上でキモなわけであるが、このデータベース自体の運用ノウハウが足りな過ぎることが問題と思える。そもそも、minimongoはもともとMeteorのために作られており、(2016年7月19日訂正 Meteor 公式” This is achieved thanks to the Minimongo library”とコメントがあり、調べてみたところGithubのmWater/minimongoのものを使っている)、このMetorの推奨構成で製品レベルの成功実例みたいなものが出てこないとその実績が評価しにくいものとなっている。一方で、大きくスケールはしていない事例ではあるが、少し古いQuoraの記事によれば、海外のスタートアップではMeteorが採用されていそうなことが分かる。念のために補足すると、現状では、他データベースだと、Meteor向けMySQLパッケージなどもAtmosphereで配布されている。

またデータベースの諸々の事情に詳しい方で、中にはMongo DBそのもののパフォーマンス自体にも疑問を持っている方もおられるだろう。個人的には、基幹となるデータベースシステムには、RDBを選択したく、PostgresSQLまたはMaria DBを選びたいところだ。しかしながら、Meteorのリアクティブ動作のコアはクライアントのminimongoなので、RDBMSが出てくると少し話が違ってくる。ちなみに、Meteorの公式ブログでは、v1.2の時点の今後ロードマップとして、SQLの対応を宣言している(2015年7月の段階)。なので、MeteorでのRDBの対応状況については、完全にMeteorチームの手腕にかかっているような節があり、Meteorはvs Ruby on Railsというのは少し違っていて、RailsはRailsで、現状ではRDBを使用したサーバサイドアプリケーションを作るための、フルスタックフレームワークの最高峰であることには間違いないだろう。

あとは、製品レベルだとどうしてもロードバランシングについてが気になる。アプリケーションサーバ、データベースサーバに分けて分散処理を、というのが必要不可欠だと思うが、これのノウハウもちょっと不明だ(※あくまでmeteorコマンドでチュートリアルのTodoアプリを立ち上げた感覚値)。これは探せば出てくるのかもしれないが、なんとなくノウハウや実績が少なそう。ちょっと探した感じMeteorHacksというサイトがあり、ここを当たればいくつかの負荷分散の方法はありそうなことは分かるのだが、なんか特殊そうだなーという印象を持った(どうやら、Nginxなどでリバースプロキシして、クラスタリングに対応するmeteorhacks/clusterを使ってMeteorを動かすといけるようである)。ちなみに、2016年9月7日時点の「meteor load balancing」というキーワードでのGoogleの検索結果では、やはりmeteorhacks/clusterについての記事が多くヒットするようだ。なので、もしかしたら感覚値通りに、Meteorはロードバランシングのやり方も少し特殊で、方法が限られてくるのかもしれない。

しかしながら、この記事の一つ前の記事(1)でも紹介しているとおり、Meteorはスタートアップの登竜門Y Combinatorの卒業生で、短期間でGithubのスター数でRailsを抜き去る勢いを持っている側面があり、運用の実績値というのは短期間で出しにくいという事実を加味すると、Meteorは今後やはり見逃せない存在である。

2016年7月19日追記

リレーショナルDBでMeteorにてリアクティブ動作と謳っているパッケージを書いておく。リレーショナルDBでもリアクティブ動作はするようだ。

numtel/meteor-mysql: Reactive MySQL for Meteor

https://github.com/numtel/meteor-mysql

numtel/meteor-pg: Reactive PostgreSQL for Meteor

https://github.com/numtel/meteor-pg

こちらもどうぞ

モバイルバージョンを終了