Murayama blog.

プログラミング教育なブログ

Servletの非同期処理

JAX-RSの非同期処理を実装しようと思ったけど、そもそもServletの非同期処理を実装したことなかったのでそっちの復習から。

参考記事

こちらの記事を参考にしてみました。

ちなみに参考にしたページではServletContextListenerでThreadPoolExecutor作ってる。要るのかわからんのでひとまずなしで。

動作確認

Tomcatを起動してブラウザでこんなかんじでアクセスする。

http://localhost:8080/async_servlet/AsyncLongRunningServlet?time=3000

この場合処理に3秒かかるわけですが、サーバのコンソールをみるとサーブレットスレッドがすぐ終了してるのがわかる。

AsyncLongRunningServlet Start::Name=25::ID=25
AsyncLongRunningServlet End::Namehttp-nio-8080-exec-3::ID=25::Time=13ms.

ちょっと疑問

サーバのコンソールを見ると、StartとEndの出力でスレッド名が変わってるんだけどこういうもんなのかな。

20分くらいでできるVagrantによるRails開発環境の構築

はじめに

Railsの環境をパパっと作りたい。でもVagrantもChefもよくわからない。そんな私でもすぐできました。

こちらが参考になりました。

以下内容をまとめてみました。

Using Vagrant for Rails Development

Overview

読み終えるまでにかかる時間は20分くらい

Vagrantは開発環境のセットアップを自動化するツールです。あなたのコンピュータ上に仮想環境を構築します。Vagrantを使えば開発環境とプロダクションサーバの整合性を保ったり、同僚と同じ環境で開発したりできるようになります。

仮想環境上に構築したRails開発環境は、ホストコンピュータの設定がいかなる状態になろうとも、同じ状態を維持できます。もし、1年後に開発プロジェクトを再度稼働する必要が生じた場合にも、数分でプロジェクトを起動できます。これは大変便利なことです。

ここでは仮想環境上の開発環境の自動構築にChefを使用します。ホストコンピュータ上にRubyと依存するパッケージがセットアップされているか注意してください。

セールストークは十分でしょう。それでは始めましょうか。

Setting Up Vagrant

Vagrantで仮想環境上でRailsアプリケーションを起動するためにはゲストOSを起動しなければなりません。そのため、ホストコンピュータ上で1GB、2GB程度のRAMが利用できることを確認しておいてください。

最初のStepはコンピュータ上にVagrantVirtualBoxをインストールしてください。

  1. Install Vagrant
  2. Install VirtualBox

VirtualBox仮想マシンを実行するソフトウェアです。VirtualBoxはバックグラウンドで動作するヘッドレスなソフトウェアとして起動できるので、ゲストOSとSSHを使ってインタラクティブにやりとりすることができます。

次はVagrantに2つのプラグインをインストールします。

  • vagrant-vbguest VirtualBox Guest Additionsを自動的にインストールします
  • vagrant-librarian-chef マシンの起動時にchefを自動的に実行します
vagrant plugin install vagrant-vbguest
vagrant plugin install vagrant-librarian-chef

インストールには少し時間がかかるでしょう。

Create the Vagrant config

ChefをセットアップするためにRailsプロジェクトのトップに移動し、次のコマンドを実行します。

cd MY_RAILS_PROJECT # Change this to your Rails project directory
vagrant init
touch Cheffile

ここで作成されたVagrantfileとCheffileをカスタマイズすることになります。

Your Cheffile

ここではCheffileを定義します。CheffileはRailsのGemfileとよく似ています。このファイルにはプロジェクトに必要なChefのクックブックを定義します。後ほど作成するVagrantfileによって、Vagrantはこれらのクックブックを参照することで環境を構築するようになります。

Cheffileに次のコードをペーストすると良いでしょう。

site "http://community.opscode.com/api/v1"

cookbook 'apt'
cookbook 'build-essential'
cookbook 'mysql'
cookbook 'ruby_build'
cookbook 'nodejs', git: 'https://github.com/mdxp/nodejs-cookbook'
cookbook 'rbenv', git: 'https://github.com/fnichol/chef-rbenv'
cookbook 'vim'

Your Vagrantfile

Vagrantfileには仮想マシン上のOSとChefの設定を定義します。

ゲストOSにはUbuntu 14.04 trusty 64-bitを使用します(32-bitがよければtrusty32でもOKです)。ここでは2GBのメモリを定義しています。続いてrails serverを実行した際、ブラウザでアクセスしやすくするため、ホストOS上の3000番ポートを仮想マシンの3000番ポートにフォワードするように設定しています。最後に仮想マシン内にChef、Ruby2.1.2、MySQLをセットアップしています。

Vagrantfileは次のように変更してください。

# -*- mode: ruby -*-
# vi: set ft=ruby :

VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  # Use Ubuntu 14.04 Trusty Tahr 64-bit as our operating system
  config.vm.box = "ubuntu/trusty64"

  # Configurate the virtual machine to use 2GB of RAM
  config.vm.provider :virtualbox do |vb|
    vb.customize ["modifyvm", :id, "--memory", "2048"]
  end

  # Forward the Rails server default port to the host
  config.vm.network :forwarded_port, guest: 3000, host: 3000

  # Use Chef Solo to provision our virtual machine
  config.vm.provision :chef_solo do |chef|
    chef.cookbooks_path = ["cookbooks", "site-cookbooks"]

    chef.add_recipe "apt"
    chef.add_recipe "nodejs"
    chef.add_recipe "ruby_build"
    chef.add_recipe "rbenv::user"
    chef.add_recipe "rbenv::vagrant"
    chef.add_recipe "vim"
    chef.add_recipe "mysql::server"
    chef.add_recipe "mysql::client"

    # Install Ruby 2.1.2 and Bundler
    # Set an empty root password for MySQL to make things simple
    chef.json = {
      rbenv: {
        user_installs: [{
          user: 'vagrant',
          rubies: ["2.1.2"],
          global: "2.1.2",
          gems: {
            "2.1.2" => [
              { name: "bundler" }
            ]
          }
        }]
      },
      mysql: {
        server_root_password: ''
      }
    }
  end
end

Running Vagrant

以上でVagrantとChefの設定は完了です。Vagrant仮想マシンを起動してsshで接続してみましょう。

# The commented lines are the output you should see when you run these commands

vagrant up
#==> default: Checking if box 'ubuntu/trusty64' is up to date...
#==> default: Clearing any previously set forwarded ports...
#==> default: Installing Chef cookbooks with Librarian-Chef...
#==> default: The cookbook path '/Users/chris/code/test_app/site-cookbooks' doesn't exist. Ignoring...
#==> default: Clearing any previously set network interfaces...
#==> default: Preparing network interfaces based on configuration...
#    default: Adapter 1: nat
#==> default: Forwarding ports...
#    default: 3000 => 3000 (adapter 1)
#    default: 22 => 2222 (adapter 1)
#==> default: Running 'pre-boot' VM customizations...
#==> default: Booting VM...
#==> default: Waiting for machine to boot. This may take a few minutes...
#    default: SSH address: 127.0.0.1:2222
#    default: SSH username: vagrant
#    default: SSH auth method: private key
#    default: Warning: Connection timeout. Retrying...
#==> default: Machine booted and ready!
#==> default: Checking for guest additions in VM...
#==> default: Mounting shared folders...
#    default: /vagrant => /Users/chris/code/test_app
#   default: /tmp/vagrant-chef-1/chef-solo-1/cookbooks => /Users/chris/code/test_app/cookbooks
#==> default: VM already provisioned. Run `vagrant provision` or use `--provision` to force it
vagrant ssh
#Welcome to Ubuntu 14.04 LTS (GNU/Linux 3.13.0-24-generic x86_64)
#
# * Documentation:  https://help.ubuntu.com/
#
# System information disabled due to load higher than 1.0
#
#  Get cloud support with Ubuntu Advantage Cloud Guest:
#    http://www.ubuntu.com/business/services/cloud
#
#
#vagrant@vagrant-ubuntu-trusty-64:~$

初めてvagrant upコマンドを実行するときは時間がかかります。これはChefの設定ファイルに従って仮想マシンをプロビジョニングするためです。次回以降のvagrant upコマンドはChefを実行する必要なくなるため、起動に時間はかからなくなるでしょう。

VagrantfileやCheffileを編集した場合は、次のコマンドを実行してマシンを再設定しなければなりません。

vagrant provision

Using Rails inside Vagrant

Vagrantは/vagrantフォルダを仮想マシンとホストOS間で共有します。cd /vagrantで移動してlsコマンドを叩けばRailsアプリケーションのファイルを確認できるでしょう。

このディレクトリでbundleコマンドを実行すればgemをインストールできますし、rake db:create && rake db:migrateを実行すればデータベースを構築しマイグレーションも行えます。

このディレクトリでrails serverコマンドを実行すればRailsアプリケーションが3000番ポートで起動します。いつもどおりlocalhost:3000にアクセスすればRailsアプリケーションを操作できます。

Conclusion

Vagrantはポータブルな開発環境を作成するソフトウェアツールです。そこにはあなたのコードを含むこともできます。Chefを使ってシンプルな設定を施すだけで、ほとんど時間をかけずに仮想マシンを起動することができます。

もし、あなたが再びVagrantマシンをセットアップする必要がでてきたり、同僚がセットアップを必要とする場合は、vagrant upコマンドを実行するだけで仮想マシンを手に入れることができます。

5分で学ぶMaven

これは何か

Maven – Maven in 5 Minutesを参考にまとめたものです。

MavenMaven in 5 Minutes

Installation

MavenJavaツールです。そのため以降の作業を続けるにはJavaのインストールは事前に済ませておいてください。Mavenダウンロードはこちらから、インストールについてはこちらを参考してください。

Mavenのインストールが完了したら、ターミナルやコマンドプロンプトから次のように入力します。

% mvn --version

そうするとインストールされたMavenのバージョンが表示されるでしょう。たとえばこんな具合に。

Apache Maven 3.2.3 (33f8c3e1027c3ddde99d3cdebad2656a31e8fdf4; 2014-08-12T05:58:10+09:00)
Maven home: /usr/local/Cellar/maven/3.2.3/libexec
Java version: 1.7.0_04-ea, vendor: Oracle Corporation
Java home: /Library/Java/JavaVirtualMachines/1.7.0.jdk/Contents/Home/jre
Default locale: ja_JP, platform encoding: UTF-8
OS name: "mac os x", version: "10.9.4", arch: "amd64", family: "mac"

ネットワークの状態によっては追加の設定が必要な場合もあります。その場合はGuide to Configuring Mavenを参考にしてください。Windowsの場合はWindows Prerequisitesを確認しておくと良いでしょう。

Creating a Project

Mavenでプロジェクトを開始するためにディレクトリを作成する必要があります。コマンドラインで次のMavenゴールを実行します。

mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Mavenをインストールしてすぐに上記のコマンドを実行すると時間がかかる場合があります。これは最新のアーティファクトプラグイン、jar、その他のファイルなどの総称)をローカルリポジトリにダウンロードする必要があるためです。場合によっては、タイムアウトする可能性があるので、その場合は2、3回コマンドを実行する必要があるでしょう。

コマンドが終了するとarchetype:generateゴールによって、artifactIdに指定した値と同名のディレクトリが生成されます。続いて生成されたディレクトリに移動します。

% cd my-app

生成されたディレクトリはMavenの提唱するstandard project structureに従った構成になっています。

my-app% tree
.
├── pom.xml
└── src
    ├── main
    │   └── java
    │       └── com
    │           └── mycompany
    │               └── app
    │                   └── App.java
    └── test
        └── java
            └── com
                └── mycompany
                    └── app
                        └── AppTest.java

src/main/javaディレクトリにはプロジェクトのソースコードを格納します。 src/test/javaディレクトリにはテストコードを格納します。pom.xmlには後述するプロジェクトのProject Object Modelを記述します。

The POM

pom.xmlファイルはMavenのプロジェクト管理の核となるファイルです。プロジェクトをビルドするための多くの重要事項を含む唯一のファイルです。POMは複雑でややこしいことを実現できますが、今はすべてを理解する必要はありません。このプロジェクトのPOMファイルは次のようになっているでしょう。

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>my-app</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

What did I just do?

Mavenのゴールであるarchetype:generateを実行しました。その際にいくつかのパラメータを渡しました。接頭辞のarchetypeプラグインであり、プラグインはゴールを含んでいます。もしAntを知っているなら、Antのタスクと似ていると感じたかもしれません。このゴール(archetype:generate)は指定したarchetypeに基づいてシンプルなプロジェクトを生成しました。今のところプラグインについては、一般的な目標を持ったゴールのコレクションであると考えておけば良いでしょう。たとえばjboss-maven-pluginの場合は、JBossの様々な処理を扱うためのプラグインです。

Build the Project

% mvn package

上記のコマンドを実行するといくつかのアクションが表示され、最終的には次のような表示になります。

 ...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 seconds
[INFO] Finished at: Thu Jul 07 21:34:52 CEST 2011
[INFO] Final Memory: 3M/6M
[INFO] ------------------------------------------------------------------------

最初に実行したコマンド(archetype:generate)と違って、2つ目のコマンドはpackageという単語一つでシンプルになっています。これはゴールではなくフェーズと呼びます。フェーズは、ビルドライフサイクルにおける一つのステップを意味します。一連のフェーズは順序付けられています。Mavenはフェーズを受け取ると、指定されたフェーズまでの一連のフェーズをすべて実行します。たとえばcompileフェーズを指定した場合、実際に実行されるフェーズは次のようになります。

  1. validate
  2. generate-sources
  3. process-sources
  4. generate-resources
  5. process-resources
  6. compile

パッケージされた最新のJARファイルを使ってテストを行うには次のように実行します。

java -cp target/my-app-1.0-SNAPSHOT.jar com.mycompany.app.App

次のようなお約束の出力を確認できるでしょう。

Hello World!

Running Maven Tools

Maven Phases

デフォルトのライフサイクルで実行されるフェーズは以下のとおりです。

  • validate:プロジェクトを検証して必要な情報が利用可能か確認する。
  • compile:プロジェクトのソースコードコンパイルする。
  • test:ユニットテスティングフレームワークを使ってコンパイルされたソースコードをテストする。これらのテストはパッケージやデプロイに不要なものとすべきである。
  • package:コンパイル済みのコードをJARファイルのような配布フォーマットにパッケージする。
  • integration-test:インテグレーションテスト環境にパッケージをデプロイする。
  • verify:パッケージを検証し、品質判定基準を満たしているか確認する。
  • install:ローカルリポジトリにパッケージをインストールし、多のプロジェクトから依存関係を解決できるようにする。
  • deploy:最新のパッケージをリモートリポジトリにコピーする。これにより、他の開発者やプロジェクトでパッケージを共有できるようになる。

また、Mavenには上記のデフォルトのリストの他に2つのライフサイクルがあります。

  • clean:ビルドによって作成されたアーティファクトを削除する。
  • site: このプロジェクトのドキュメントサイトを生成する。

フェーズは実際にはゴールにマッピングされます。フェーズごとに実行される具体的なゴールは、プロジェクトのパッケージタイプに依存しています。たとえばプロジェクトのタイプがJARの場合、packageフェーズでjar:jarゴールを実行します。同様にプロジェクトタイプがWARの場合は、packageフェーズでwar:warゴールを実行します。

面白いことにフェーズとゴールは順序立てて実行できます。

mvn clean dependency:copy-dependencies package

このコマンドはプロジェクトをクリーンし、依存関係をコピーします。それからパッケージを作成します(もちろんパッケージまでの前フェーズはすべて実行されます)。

Generating the Site

mvn site

このフェーズはプロジェクトのpomの情報に基づいてサイトを生成します。生成されたドキュメントはtarget/siteで確認できます。

Conclusion

駆け足での紹介でしたが、このガイドを通じてMavenの多彩さに興味を持ってもらえると幸いです。これはあくまでクイックスタートガイドだという点だけは念を押しておきます。とはいえ、Mavenの詳細を学ぶ準備は整いました。Maven Getting Started Guideも参考にしてください。

Rails4.1でSpring、Rspec、Guardを使う

はじめに

Rails4.1でテスト環境を構築する方法を調べてました。以下のブログ(英語、、)が参考になったので紹介します。
Setup Rails 4.1, Spring, Rspec, and Guard - Girders

Setup Rails 4.1, Spring, Rspec, and Guard

Rails4.1では、Springを使うことでrailsコマンドやrakeコマンドの起動を素早く実行することができます。Springはアプリケーションのバックグラウンドで動作し、アプリケーション起動時のもろもろの動作をサポートします。Springに関連する情報を見つけるのに苦労してので、ここにまとめておきます。

私はテストフレームワークRSpec、テストランナーのイベントウォッチャーにGuardを使っています。Springを使えば、エディタで編集したファイルを速やかにテストしてくれるようになります。

はじめに検証用のアプリケーションを作るところから始めましょう。もちろん、既存のアプリケーションでもこれから紹介する方法で上手くいくと思います。Rails 4.1.0.beta1 on Ruby 2.1.0p0で動作検証済みです。

% rails new myapp -T
% cd myapp
% vim Gemfile

Gemfileを編集し次のgemを追記します。

  • rspec-rails - Rails用のRSpecテスティングフレームワーク
  • spring-commands-rspec - springにrspecコマンドを追加する。Railsアプリケーションのプレローダ機能も含む
  • guard-rspec - rspecを実行するためにGuardファイルウォッチャ
  • rb-fsevent - OS/Xでのみ必要。Mac OS/X FSEvents APIを通じでファイルの変更を監視する

Gemfileには次のように記述するとよいでしょう。(もちろんgemのバージョンも記述してもかまいません)

group :development, :test do
  gem 'spring-commands-rspec'
  gem 'rspec-rails'
  gem 'guard-rspec'
  gem 'rb-fsevent' if `uname` =~ /Darwin/
end

"Bundle"をもう一度実行して、依存関係のあるgemをインストールします。

% bundle install

Setup Spring

springコマンドは、springアプリケーションランナーを介してコマンドを実行します。デフォルトでは、railsコマンドrakeコマンドのみ設定されています。ここにプラグイン(gem)を追加すれば、rspecコマンドを実行することができるようになります。

springにはbinstubというサブコマンドがあります。このサブコマンドを使えばmyapp/binのスクリプトを更新できます。

springでrspecコマンドを利用できるようにするために、次のコマンドを実行します。

% spring binstub --all
* bin/rake: spring already present
* bin/rspec: generated with spring
* bin/rails: spring already present

springがバックグラウンドで起動しているかどうかはstatusサブコマンドで確認できます。springが有効となっているコマンドを使ってサーバを起動してみましょう。

springの初回起動時はRailsのイニシャライザを実行するので時間がかかります。springはアプリケーションサーバを起動したら、結果を速く返すためにプロセスをフォークしています。

% spring status
Spring is not running.
% rails g
...
% spring status
Spring is running:

89168 spring server | myapp | started 5 secs ago
89170 spring app    | myapp | started 5 secs ago | development mode

springサーバはshellのセッションが終了するときに自動的にシャットダウンされます。明示的にシャットダウンしたい場合は、次のようにコマンドを入力します。

% spring stop
Spring stopped.

springを利用するのに必須となる知識はこれだけです。

Setting up Rspec and Guard

このプロセスも通常のやり方と同じです。

% rails g rspec:install
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
% guard init
11:32:14 - INFO - Writing new Guardfile to /Users/allen/src/myapp/Guardfile
11:32:14 - INFO - rspec guard added to Guardfile, feel free to edit it
% vim Guardfile

上記のコマンドでrspecとguardに必要なファイルをセットアップします。次に、guardに対してrspecを使うときにspring runnerを使用することを伝えなければなりません。Guardfileを編集して、rspecコマンドをspring rspecコマンドに変更します。

guard :rspec do

上記の部分を次のように変更します。

guard :rspec, cmd:"spring rspec" do

Running

(ここで紹介する実行方法は、binstubs、shell、aliasなど、コマンドの実行方法はあなたの環境に依存します。bundle execやbin/railsのように環境に合わせて解釈してください)

springを使用するとrspecがどの程度速くなるのかチェックしてみましょう。ここではtimeコマンドを使います。

% time spring rspec
...
spring rspec  0.12s user 0.07s system 6% cpu 3.126 total

% time spring rspec
...
spring rspec  0.12s user 0.06s system 30% cpu 0.600 total

上記の実行結果は一つのscaffoldリソースをもつシンプルなアプリケーションで試したものです。アプリケーションが大きくなるに連れて、初回起動に必要な時間は長くなります。つまり、節約できる時間も増えていくことでしょう。

TDDを始めるなら、ターミナルセッションでguardを起動しましょう。

% guard
11:48:02 - INFO - Guard is using TerminalTitle to send notifications.
11:48:02 - INFO - Guard::RSpec is running
11:48:02 - INFO - Guard is now watching at '/Users/allen/src/myapp'
[1] guard(main)>

アプリケーションのファイルがguardによって監視され、specやsuiteが変更されるたびに、spring rspecコマンドが実行されるようになります。

gemファイルの追加など、キャッシュされているローカルの依存関係を更新したい場合は、guardとspringを再起動する必要があります(guardは"ctrl-D"、springはspring stopを実行するなど)。guardを再起動すればspringも再起動されるので、依存関係がリロードすることができるでしょう。

Parse - Relations

Relations Guide

リレーションには3つの種類があります。One-to-oneリレーションは1つのオブジェクトを他のオブジェクトに関連付けます。One-to-manyリレーションは1つのオブジェクトに複数のオブジェクトに関連付けます。many-to-manyリレーションは複数のオブジェクト間の複雑な関連を表します。

Relationships in Parse

Parseでリレーションを定義するには4つの方法があります。

  • Pointers (one-to-one、one-to-manyリレーション向け)
  • Arrays (one-to-many、many-to-manyリレーション向け)
  • Parse Relations (many-to-manyリレーション向け)
  • Join Tables (many-to-manyリレーション向け)

One-to-Many Relationships

one-to-manyリレーションのPointers、Arraysを検討する場合はいくつかの検討事項があります。まずリレーションにどれだけの数のオブジェクトが関連するかを検討します。リレーションの"many"サイドにたくさんの数(100件を超えるような)のオブジェクトを含むなら、Pointersを選択してください。100件より少ない数のオブジェクトを扱うならArraysは便利です。とくに、親オブジェクトの取得と同時にすべての子オブジェクトを扱いたいケースで役に立つでしょう。

Using Pointers for One-to-Many Relationships

ゲームアプリケーションを開発しているとしましょう。ゲームではプレイするごとにスコアや結果を記録します。Parse上ではGameオブジェクトの中にこのデータを保存します。ゲームが上手くいくと、個々のプレイヤーはシステム内に数千のGameオブジェクトを格納します。このように、大きな数のリレーションが発生する可能性がある状況においては、Pointersを選択すると良いでしょう。

このゲームアプリケーションでは、Parse UserにすべてのGameオブジェクトを関連付けるものとします。次のように実装できます。

ParseObject game = new ParseObject("Game");
game.put("createdBy", ParseUser.getCurrentUser());

Parse Userによって生成されたGameオブジェクトを取得するクエリは次のようになります。

ParseQuery<ParseObject> gameQuery = ParseQuery.getQuery("Game");
gameQuery.whereEqualTo("createdBy", ParseUser.getCurrentUser());

特定のGameオブジェクトを生成したParse Userを取得したい場合は、createdByキーでルックアップします。

// say we have a Game object
ParseObject game = ...
 
// getting the user who created the Game
ParseUser createdBy = game.getUser("createdBy");

多くの場合において、Pointersはone-to-manyリレーションを実現するための良い選択肢となるでしょう。

Using Arrays for One-to-Many Relations

Arraysはone-to-manyリレーションにおいて、オブジェクトの数が少ないとわかっている場合に有効です。Arraysは、includeKeyパラメータを使うことでメリットが生まれます。パラメータを適用すると"one-to-many"リレーションにおける"one"オブジェクトを取得する際に、"many"に該当するすべてのオブジェクトを取得するこができます。ただし、関連するオブジェクトの数が多くなるとレスポンスタイムがかかってしまいます。

ゲームアプリケーションの場合、プレイヤーが手に入れた武器を記録する必要があるでしょう。この武器の数は1ダース程度に収まるものとします。このようなケースでは、武器の数が極端に多くならないことがわかっています。また、プレイヤーは画面に表示する武器の並び順を指定できるとしましょう。扱う配列の数が少ないとわかっていて、プレイするごとにユーザが並び順を設定したいような場合においてArraysは上手く動作します。

Parse UserオブジェクトにweaponsListフィールドを作成してみましょう。

// let's say we have four weapons
ParseObject scimitar = ...
ParseObject plasmaRifle = ...
ParseObject grenade = ...
ParseObject bunnyRabbit = ...
 
// stick the objects in an array
ArrayList<ParseObject> weapons = new ArrayList<ParseObject>();
weapons.add(scimitar);
weapons.add(plasmaRifle);
weapons.add(grenade);
weapons.add(bunnyRabbit);
 
// store the weapons for the user
ParseUser.getCurrentUser().put("weaponsList", weapons);

Weaponオブジェクトのリストを取得する場合は次のようになります。

ArrayList<ParseObject> weapons = ParseUser.getCurrentUser().get("weaponsList");

one-to-manyリレーションにおいて、"one"オブジェクトをフェッチする際に"many"オブジェクトをすべてフェッチしたいケースもあるでしょう。
Parseはこのようなケースを想定して、ParseQueryにincludeKeyパラメータ(Androidの場合はincludeメソッド)を提供しています。これにより、Parse User取得時にWeaponオブジェクトのリストをフェッチできます。

// set up our query for a User object
ParseQuery<ParseUser> userQuery = ParseUser.getQuery();
 
// configure any constraints on your query...
// for example, you may want users who are also playing with or against you
// tell the query to fetch all of the Weapon objects along with the user
// get the "many" at the same time that you're getting the "one"
userQuery.include("weaponsList");
 
// execute the query
userQuery.findInBackground(new FindCallback<ParseUser>() {
  public void done(List<ParseUser> userList, ParseException e) {
    // userList contains all of the User objects, and their associated Weapon objects, too
  }
});

one-to-manyリレーションの"many"側から、"one"オブジェクトを取得できます。たとえば、特定のWeaponを持っているParseUserを見つけたいなら、次のようにクエリに制約を実装します。

// add a constraint to query for whenever a specific Weapon is in an array
userQuery.whereEqualTo("weaponsList", scimitar);
 
// or query using an array of Weapon objects...
userQuery.whereEqualTo("weaponsList", arrayOfWeapons);

Many-to-Many Relationships

続いてmany-to-manyリレーションシップを見てみましょう。読書アプリケーションを開発しているとして、Book ObjectsとAuthor Objectsのようなモデルがあるとします。Authorは複数のBookを記述し、Bookは複数のAuthorを持ちます。このようなmany-to-manyシナリオでは、Arrays、Parse Relations、Join Tableの生成といった選択肢があります。

これらの選択のポイントは、2つのエンティティ間のリレーションシップにメタデータを保持するかどうかです。メタデータを保持しないなら、Parse RelationやArraysを使うことが好ましいでしょう。一般的に配列を使うと、パフォーマンスが高くなり、より少ないクエリで済むようになります。many-to-manyリレーションシップのいずれかの配列要素数が100を超える場合は、Pointersと同じ理由でParse RelationやJoin Tablesを使う方が好ましいでしょう。

一方、リレーションにメタデータを保持する場合は、セパレートテーブル("Join Table")を用意して両方のリレーションを格納するようにします。これはリレーションについての情報であり、片方のリレーションのオブジェクトについての情報ではないという点に注意してください。メタデータとして、Join Tableに定義するものには次のようなものがあります。

  • リレーションが作成された日時
  • リレーションを作成したユーザ
  • リレーションが参照された回数

Parse Relations

Parse Relationsを使うと、BookといくつかのAuthorオブジェクトの間にリレーションを定義できます。Data Browserでは、Bookオブジェクトにauthorsという名前のリレーション型の列を作成できます。

その後、BookオブジェクトにいくつかのAuthorsオブジェクトを関連付けます。

// let’s say we have a few objects representing Author objects
ParseObject authorOne =
ParseObject authorTwo =
ParseObject authorThree =
 
// now we create a book object
ParseObject book = new ParseObject("Book");
 
// now let’s associate the authors with the book
// remember, we created a "authors" relation on Book
ParseRelation<ParseObject> relation = book.getRelation("authors");
relation.add(authorOne);
relation.add(authorTwo);
relation.add(authorThree);
 
// now save the book object
book.saveInBackground();

あるBookを書いたAuthorのリストを取得するクエリは次のようになります。

// suppose we have a book object
ParseObject book = ...
 
// create a relation based on the authors key
ParseRelation relation = book.getRelation("authors");
 
// generate a query based on that relation
ParseQuery query = relation.getQuery();
 
// now execute the query

あるAuthorの寄稿したBookの一覧を取得したいこともあるでしょう。リレーションの逆を取得するクエリを作成します。

// suppose we have a author object, for which we want to get all books
ParseObject author = ...
 
// first we will create a query on the Book object
ParseQuery<ParseObject> query = ParseQuery.getQuery("Book");
 
// now we will query the authors relation to see if the author object we have 
// is contained therein
query.whereEqualTo("authors", author);

Using Join Tables

リレーションについてもっと知りたくなるようなケースもあるでしょう。たとえば、userが他のuserをフォローするようなuser間のfollowing/followerのリレーションです。これはソーシャルネットワークでポピュラーなものです。私たちのアプリケーションでは、User AがUserBをフォローしたという情報だけでなく、いつフォローを開始したかという情報も知りたいとしましょう。このような情報は、Parse Relationに含むことができません。このような情報を保存するためには、リレーションシップが記録されるセパレートテーブルを作成しなければなりません。このテーブルをFlollowテーブルと呼びましょう。Followテーブルにはfromカラムとtoカラムがあり、これらはParse Userへのポインタを保持します。これらのリレーションと並んで、dateという名前のDate型のカラムを追加します。

2人のユーザのフォロー関係を保存する場合、from、to列、date列を適切に入力して、Followテーブルに行を追加します。

// suppose we have a user we want to follow
ParseUser otherUser = ...
 
// create an entry in the Follow table
ParseObject follow = new ParseObject("Follow");
follow.put("from", ParseUser.getCurrentUser());
follow.put("to", otherUser);
follow.put("date", Date());
follow.saveInBackground();

フォローしている全員を取得したいなら、Followテーブルに次のようなクエリを実行します。

// set up the query on the Follow table
ParseQuery<ParseObject> query = ParseQuery.getQuery("Follow");
query.whereEqualTo("from", ParseUser.getCurrentUser());
 
// execute the query
query.findInBackground(newFindCallback<ParseObject>() {
    public void done(List<ParseObject> followList, ParseException e) {
 
    }
});

同様にフォロワーを取得する場合も簡単です。

// set up the query on the Follow table
ParseQuery<ParseObject> query = ParseQuery.getQuery("Follow");
query.whereEqualTo("to", ParseUser.getCurrentUser());
 
// execute the query
query.findInBackground(newFindCallback<ParseObject>() {
    public void done(List<ParseObject> followList, ParseException e) {
 
    }
});

Using Arrays for Many-to-Many Relations

Arraysは、One-to-Manyリレーションで使用したように、Many-to-Manyリレーションでも使用できます。リレーションの片方のすべてのオブジェクトは、Array型のカラムを持ち、リレーションの他方のいくつかのオブジェクトを保持します。

読書アプリケーションにおけるBookとAuthorオブジェクトを考えてみましょう。Bookオブジェクトは、AuthowオブジェクトのArrayを含むでしょう(authorsのようなカラム名で)。このシナリオではArraysは有効に働きます。なぜなら、Authorオブジェクトが100件を超える可能性が非常に低いからです。このような理由からBookオブジェクトにArrayを定義します。結果的に、Authorは100冊以上のBookと関連付けることもできます。*1

BookとAuthor間のリレーションを保存する例を見てみましょう。

// let's say we have an author
ParseObject author = ...
 
// and let's also say we have an book
ParseObject book = ...
 
// add the song to the song list for the album
book.put("authors", author);

AuthorリストはArrayであるため、Bookのフェッチ時にincludeKey(Androidの場合はincludeメソッド)を使うことができます。そうするとbookのフェッチ時にauthorsも合わせて取得できます。

// set up our query for the Book object
ParseQuery bookQuery = ParseQuery.getQuery("Book");
 
// configure any constraints on your query...
// tell the query to fetch all of the Author objects along with the Book
bookQuery.include("authors");
 
// execute the query
bookQuery.findInBackground(newFindCallback<ParseObject>() {
    public void done(List<ParseObject> bookList, ParseException e) {
    }
});

この時点で、BookオブジェクトからすべてのAuthor Objectsを取得できます。

ArrayList<ParseObject> authorList = book.getList("authors");

最後に、あなたはAuthorを知っていて、そのAuthorの寄稿したすべてのBookオブジェクトを取得したいとします。次のようにクエリに制約をかけます。

// set up our query for the Book object
ParseQuery bookQuery = ParseQuery.getQuery("Book");
 
// configure any constraints on your query...
booKQuery.whereEqualTo("authors", author);
 
// tell the query to fetch all of the Author objects along with the Book
bookQuery.include("authors");
 
// execute the query
bookQuery.findInBackground(newFindCallback<ParseObject>() {
    public void done(List<ParseObject> bookList, ParseException e) {
 
    }
});

One-to-One Relationships

Parseでは、one-to-oneリレーションは、あるオブジェクトを2つのオブジェクトに分離したいケースに使用します。このようなケースは稀ですが、次のようなケースが考えられます。

  • ユーザデータの可視性に制限をかけたい場合。このようなケースでは、他のユーザに公開しても構わないユーザ情報と、他のユーザに公開したくないプライベートな情報に分離します(ACLを使ってプロテクトします)。
  • サイズによるオブジェクトの分離。オブジェクトの最大サイズである128Kを超えてしまうような場合は、データの一部を分離するためにセカンダリオブジェクトを定義します。データを分離しなければならないような大規模なオブジェクトを設計するのは避けるべきでしょう。このような問題を回避できない場合は、Parse Fileによる大規模データの保存を検討してください。


ここまで読んでくれてありがとう。このように複雑になってしまうことは申し訳なく思っています。一般的にデータモデリングは複雑になりがちです。良く言えば、人間関係に比べればまだマシでしょうか。*2

*1:この一文は要るのかな。。

*2:これも要らんかも。。

Parse - Cloud Code

What is Cloud Code?

Parseのビジョンは、モバイルアプリを開発するデベロッパーから、サーバーの取り扱いをなくすことです。複雑なアプリケーションの場合は、ときとしてモバイルデバイス上で実行できないロジックが必要になります。Cloud Codeはこのような課題を解消します。

Cloud Codeの使い方は簡単です。なぜなら、アプリの開発で利用してきたJavaScript SDKをそのまま利用できるからです。モバイルデバイス上で動作するのか、Parse Cloud上で動作するのかという違いだけです。Cloud Codeはアップデートすると、モバイル環境へすぐに反映できるので、アプリケーションのリリースを待つ必要がなくなります。これにより、アプリケーションの更新が簡単になり、新機能も素早く追加できようになるでしょう。

あなたがモバイル開発に熟知しているとしても、Cloud Codeの理解しやすさ、簡単さを実感してもらえることでしょう。

Parse - Cloud Code - Command Line Tool

参照元
https://parse.com/docs/cloud_code_guide#clt

Command Line Tool

ここまでクラウド上にコードをデプロイするParseのコマンドラインツールを見てきました。コマンドラインツールには、他にも幾つか役に立つものが用意されています。コマンドラインツールのインストール手順については、Installing the Toolを参照してください。

Introduction

Parseでは、同じコードを複数のアプリケーションにデプロイできます。これにより、"development"アプリケーション、"production"アプリケーションのような使い分けが可能です。開発中のコードをproductionアプリケーションとして配布する前に、developmentアプリケーションでテストできます。

parse newコマンドで指定したアプリケーションが1つ目のデフォルトアプリケーションとなります。parse newコマンドを除くすべてのコマンドはアプリケーション名を引数にとることができます。

Deploying

新しいリリースをデプロイするには次のように実行します。

$ parse deploy
New release is named v1

この結果、新しいコード(cloud/main.js)がParseクラウドにPUSHされ、デフォルトアプリケーションにデプロイされます。追加した別のアプリケーションをターゲットとしてデプロイする場合は次のように実行します。

$ parse deploy "My Other App"
New release is named v2

リリース時に-d、--descriptionオプションを指定してリリースノートを追加することもできます。

自動テストやデプロイといった他のスクリプトにparse deployコマンドを埋めることもできます。Parseコマンドラインツールは、コマンドの実行結果をexit codeとして返します。コマンドが正常に終了した場合は、exit codeに0が返ります。デプロイに失敗した場合は0以外の値が返ります。

Developing Cloud Code

developコマンドを使えばdevelopmentモードでコマンドラインツールを実行できます。developmentモードは、ログファイルを監視するようにソースディレクトリを監視し、アップデートを検出するとParseにデプロイします。

$ parse develop development
E2013-03-19:20:17:01.423Z] beforeSave handler in release 'v1' ran for GameScore with the input:
  {"original": null, "update":{"score": 1337}}
 and failed validation with Each GamesScore must have a playerName
New release is named v58
I2013-03-19T20:17:10.343Z] Deployed v58 with triggers:
  GameScore:
    before_save

他のコマンドと違って、developコマンドはPUSH対象のアプリケーション名を必ず指定する必要があります。これは、誤ってテストしていないコードをproductionアプリケーションに適用しないようにするためです。

Add a New Target

addコマンドを実行することで、新しいparseアプリケーションをターゲットとして追加できます。このコマンドは、Parse.comに登録したEメールアドレスとパスワードを要求します。それらを入力すると選択可能なアプリケーションのリストが表示されます。

$ parse add
Email: pirate@gmail.com
Password:
1:PiecesOfEightCounter
2:BootyDivider
Select an App: 1

addコマンドには、オプション引数でアプリケーションのエイリアスを指定できます。このエイリアスは、アプリケーション名の代用として利用できます。

ほとんどのケースで、設定情報はglobal.jsonファイルに記録されます。しかし、チームの他のメンバーとシェアしたくない開発用のアプリケーションが必要なこともあるでしょう。そのような場合は、--localフラグを使うと、local.jsonファイルに記録することができます。global.jsonファイルはソース管理下に置き、local.jsonはローカルマシン内でのみ使用すると良いでしょう。

Rolling Back

parse rollbackを使うとリリースをロールバックできます。parse deployと同じようにオプション引数でターゲットを指定できます。

$ parse rollback
Rolled back to v1

こうすることで、コードを1つ前のバージョンにロールバックできます。-r、--release=オプションを使えばリリース名を指定してロールバックすることもできます。

Reading the Logs

すべてのデプロイ、ロールバック、Cloud Codeのアクティベーションはログとして残ります。parse logコマンドを使えば、最新のログを取得できます。ログには2つの種類があります。

  • INFO - すべてのログを含みます。
  • ERROR - エラーログだけを含みます。

logコマンドはオプションでターゲットを引数にとります。また3つのオプションがあります。

  • -n - 表示するログの行数(デフォルトは10行)
  • --level/-l - ログレベルを指定(デフォルトはINFO)
  • -f - tail -fのように振る舞う
$ parse log -n 1
I2012-07-10:13:37:00] beforeSave handler in release 'v1' ran for GameScore with the input:
  {"original": null, "update":{"score": 1337}}
 and failed validation with Each GamesScore must have a playerName

Listing Releases

releasesコマンドを使うと、Parseクラウドへのリリース履歴がリスト表示されます。Parseは最新の10件のリリースのみ記録しています。

Setting the SDK version

開発ディレクトリにおいて、Cloud Codeを利用する際のデフォルトのParse JavaScript SDKバージョンは、newコマンドを実行した時点の最新バージョンです。このバージョンを変更したい場合は、config/global.jsonのparseVersionを変更してください。