マルチティア・アーキテクチャーで RESTful な Web サービスと動的な Web アプリケーションを構築する

引き続き、マルチティア・アーキテクチャーを使って RESTful な Web サービスと動的な Web アプリケーションを構築する方法を学んでください。この記事では、各ティアにおけるコンポーネントを設計および作成し、これらのコンポーネントを 1 つに結合する方法を実際の手順に沿って説明します。ここで説明する例では、RESTful な Web サービスと、Ajax (Asynchronous JavaScript and XML)、Spring Web Flow とを連動させて、デスクトップのようなリッチで応答性に優れた Web インターフェースを実現します。さらに、Ruby スクリプトなどのクライアント・プログラムがサーバーに対するデータのアップロードおよびダウンロードを行うために、どのように RESTful な Web サービスを利用するかについても説明します。

はじめに

前回の記事では、RESTful な Web サービスと動的な Web アプリケーションを構築するためのマルチティア・アーキテクチャーについて説明しました。そこでの説明では、Ajax/GWT (Google Web Toolkit) と外部のクライアント・アプリケーションからの呼び出しに対しては、プレゼンテーション・レイヤーのリソース・リクエスト・ハンドラー (RRH) で対応し、ブラウザーからのリクエストに対しては、プレゼンテーション・レイヤーのブラウザー・リクエスト・ハンドラー (BRH) で処理を行ってブラウザーに表示する出力を生成することを提案しました。この 2 つのハンドラーは共通のビジネス・ロジック・レイヤーを共有し、このビジネス・ロジック・レイヤーがデータ・アクセス・レイヤーとやり取りします。サンプル・アプリケーションのアプリケーション・ティアは、Java™ コードで作成されています。この記事では、RESTful な Web サービスには Jersey フレームワークを使用し、MVC、ナビゲーション、JDBC には Spring フレームワークを使用します。また、データベースには MySQL を使用し、IDE としては Eclipse を使用します。完成したサンプル・アプリケーションは、Tomcat にデプロイします。このサンプル・アプリケーションは、NCAR (National Center for Atmospheric Research) のアドミニストレーターが NCAR の従業員を登録するために使う、単純な架空のアプリケーションです。


シナリオ

このシナリオでは、アドミニストレーターがブラウザー・インターフェースを使って NCAR の新しい従業員を登録します。NCAR には以下の 4 つのラボがあり、それぞれが各種の部門を持っています。

  • Computational and Information Systems Laboratory
  • Earth and Sun Systems Laboratory
  • Earth Observing Laboratory
  • Research Applications Laboratory

アプリケーションの登録インターフェースには、以下のフィールドがあります。

  • Username (ユーザー名)
  • Password (パスワード)
  • Last Name (苗字)
  • First Name (名前)
  • Email (E メール)
  • 当該従業員が勤務する Lab (ラボ) および Division (部門)

上記のフィールドのうち、ラボと部門の各フィールドは選択方式のメニューです。また、ユーザー名は一意でなければなりません。すでに使用されているユーザー名をアドミニストレーターが入力しようとすると、ブラウザーに警告が表示されて、ユーザー名フィールドがクリアされることになります。

部門の選択メニューに表示される項目のリストは、ラボの選択メニューで選択したラボによって異なります。最初にインターフェースを開いたときには、部門フィールドは選択できないようになっています。アドミニストレーターがラボを選択すると、部門の選択メニューは選択可能になりますが、このメニューに含まれるのは選択されたラボの部門だけです。アドミニストレーターが情報を入力して「Submit (送信)」をクリックすると、システムは新しいユーザーを MySQL データベースに追加し、正常に追加されたことを示すメッセージが表示されます。

システム・アドミニストレーターは、新規ユーザーのアップロードと登録済みユーザーのダウンロードを行うためのバッチ・プロセスを実行できなければなりません。このバッチ・プログラムは、Ruby、Python、Perl、または Java コードを使用して実装することができます。この記事では、その一例として Ruby を使用します。ブラウザー・インターフェースと RESTful な Web サービスのいずれにも、認証は必要ありません。

注: この架空のアプリケーションはまだ本番環境で使えるものではありません。現実の世界で使用するには、少なくとも例外処理、ロギング、認証、データ検証などが必要となります。


コンポーネント

表 1 に、各ティアのコンポーネントと、それらのコンポーネントがフォルダー構造内でどのように配置されているかを記載します。

表 1. フォルダー構造
ティアレイヤーおよびスクリプトファイルの場所
クライアント・ティアAjax スクリプトWebRoot/js
JSP ページWebRoot/WEB-INF/jsp
Ruby スクリプトClient/ruby
プレゼンテーション・ティアプレゼンテーション・レイヤー – ブラウザー・リクエスト・ハンドラー (BRH)src/edu/ucar/cisl/ncarUsers/presentation/brh
プレゼンテーション・レイヤー – リソース・リクエスト・ハンドラー (RRH)src/edu/ucar/cisl/ncarUsers /presentation/rrh
ビジネス・ロジック・レイヤーsrc/edu/ucar/cisl/ncarUsers/bll
データ・アクセス・レイヤーsrc/edu/ucar/cisl/ncarUsers/dal
データ・ストア・ティアMySql スクリプトdb/setup.sql

ソース・コードをダウンロードして、C ドライブに解凍してください。すると、C:\ncarUsers という新しいフォルダーが作成されます。このフォルダーの下に、表 1 に記載したフォルダー構造全体が含まれているはずです。ダウンロード・ファイルには、すべてのソース・コードに加え、MySQL Connector/J Driver 5.1、Jersey 1.0、Spring Framework 2.5.5、および Spring Web Flow 2.0.2 に必要なすべてのライブラリーが含まれています。これよりも新しいリリースを試してみる必要がない限り、このデモにはこれらのライブラリーで十分です。


環境をセットアップする

以下のソフトウェア・パッケージをダウンロードして、それぞれの Web サイト (「参考文献」にリンクを記載) で提供しているインストール・ガイドに従ってインストールします。

  1. Apache Tomcat 6.x
  2. MySQL 5.1
  3. Eclipse IDE for Java EE Developers (私が使用したのは Eclipse Europa リリースですが、これ以降のバージョンでも機能します。)
  4. Ruby

Ruby をインストールし終わったら、コマンド gem install --remote を実行して、json ライブラリー (図 1) と rest-open-uri ライブラリーをダウンロードおよびインストールします。

※訳注: 上記段落の「gem install --remote」は、原文では、「gem install –remote」となっていますが、実際には「gem install --remote」なので、そのようにしてあります。

図 1. Ruby の gem install コマンドによる json ライブラリーのインストール
Ruby の gem install コマンドによる json ライブラリーのインストール

データベースを作成する

この記事で使用するのは MySQL データベースです。MySQL サーバー・インスタンスを作成するには、MySQL Server Instance Configuration Wizard を使用します。インスタンスのデフォルト名は MYSQL です。MySQL コマンドライン・ツールを起動するために、mysql -u root -p MYSQL を実行してください (前のステップでルート・ログイン用に設定したパスワードを入力する必要があります)。続いてコマンドライン・ツールで source c:/ncarUsers/db/setup.sql を実行して、データベース (ncar_users)、MySQL ユーザー (tutorial) とパスワード (tutorial)、この記事のためのテーブルを作成します (図 2)。このスクリプトは、ラボと部門のテーブルへのデータ設定も行います。

図 2. スクリプト・ファイル setup.sql の実行
スクリプト・ファイル setup.sql の実行

Eclipse で Tomcat サーバーを構成する

以下の手順で Tomcat サーバーを構成します。

  1. Eclipse を開き、「File (ファイル)」 > 「New (新規)」 > 「Other (その他)」の順に選択します。
  2. 表示された一覧から「Server (サーバー)」を選択します。
    図 3. Eclipse での Tomcat サーバーの構成 – ステップ 1
    Eclipse での Tomcat サーバーの構成 – ステップ 1
  3. Next (次へ)」をクリックし、表示されたウィンドウで、「Apache」 > 「Tomcat v6.0 Server (Tomcat v6.0 サーバー)」の順に選択します。
    図 4. Eclipse での Tomcat サーバーの構成 – ステップ 2
    Eclipse での Tomcat サーバーの構成 – ステップ 2
  4. Next (次へ)」をクリックします。次のウィンドウで「Browse… (参照…)」をクリックし、Tomcat のインストール・ディレクトリーを選択します。
    図 5. Eclipse での Tomcat サーバーの構成 – ステップ 3
    Eclipse での Tomcat サーバーの構成 – ステップ 3
  5. Finish (完了)」をクリックします。

Eclipse で Web プロジェクト ncarUsers を作成する

以下の手順に従って、Web プロジェクトを作成します。

  1. File (ファイル)」 > 「New (新規)」 > 「Project… (プロジェクト…)」の順に選択し、表示された画面で 「Web」を開きます。
  2. Dynamic Web Project (動的 Web プロジェクト)」をクリックします。
    図 6. Eclipse での Web プロジェクト ncarUsers の作成 – ステップ 1
    Eclipse での Web プロジェクト ncarUsers の作成 – ステップ 1
  3. Next (次へ)」をクリックします。新しく表示されたウィンドウで、「Project name (プロジェクト名)」フィールドに「ncarUsers」と入力します。
    図 7. Eclipse での Web プロジェクト ncarUsers の作成 – ステップ 2
    Eclipse での Web プロジェクト ncarUsers の作成 – ステップ 2
  4. Next (次へ)」をクリックします。
  5. 「Project Facets (プロジェクト・ファセット)」ウィンドウでは「Next (次へ)」をクリックします。
  6. 「Web Module (Web モジュール)」ウィンドウで、「Content Directory (Content directory)」フィールドの値を「WebContent」から「WebRoot」に変更します。
  7. Finish (完了)」をクリックします。
    図 8. Eclipse での Web プロジェクト ncarUsers の作成 – ステップ 3
    Eclipse での Web プロジェクト ncarUsers の作成 – ステップ 3

記事に付属のダウンロードに含まれるファイルをインポートする

以下の手順に従ってファイルをインポートします。

  1. 「Project Explorer (プロジェクト・エクスプローラー)」で、「ncarUsers」を右クリックし、「Import (インポート)」 > 「Import … (インポート…)」の順に選択します。
  2. 「Import (インポート)」ウィンドウで、「General (一般)」 > 「File System (ファイル・システム)」の順にクリックします (図 9)。
    図 9. 記事に付属のダウンロード・ファイルを ncarUsers プロジェクトへインポートする – ステップ 1
    記事に付属のダウンロード・ファイルを ncarUsers プロジェクトへインポートする – ステップ 1
  3. Next (次へ)」をクリックします。
  4. 「File System (ファイル・システム)」ウィンドウで「Browse… (参照…)」をクリックし、「C:\ncarUsers」を選択します。
  5. 「ucarUsers」の隣にあるチェック・ボックスを選択します (図 10)。
    図 10. 記事に付属のダウンロード・ファイルを ncarUsers プロジェクトへインポートする – ステップ 2
    記事に付属のダウンロード・ファイルを ncarUsers プロジェクトへインポートする – ステップ 2

インポートが完了すると、「Project Explorer (プロジェクト・エクスプローラー)」の表示は図 11 のようになります。

図 11. プロジェクトのインポート結果
プロジェクトのインポート結果

以降のセクションでは、ドメイン・オブジェクト、データ・アクセス・レイヤー (DAL)、ビジネス・ロジック・レイヤー (BLL)、BRH と RRH が含まれるプレゼンテーション・レイヤー、そしてクライアント・アプリケーションを実装します。これらの説明を飛ばしたい場合は、「Eclipse からアプリケーションを実行する」のセクションまでスキップしても構いません。


ドメイン・オブジェクトを実装する

ドメイン・オブジェクトがモデル化するのは、アプリケーションの問題領域です。このサンプルでは、User (リスト 1)、Lab (リスト 2)、Division (リスト 3) という 3 つのドメイン・オブジェクトを実装しました。

リスト 1. edu.ucar.cisl.ncarUsers.domain.User
1.	package edu.ucar.cisl.ncarUsers.domain;

2.	import java.io.Serializable;

3.	public class User implements Serializable {
4.	   protected int ID;
5.	   protected String userName;
6.	   protected String password;
7.	   protected String firstName;
8.	   protected String lastName;
9.	   protected String email;
10.	   protected int lab;
11.	   protected int division;

12.	... //getters and setters
13.	}
リスト 2. edu.ucar.cisl.ncarUsers.domain.Lab
1.	package edu.ucar.cisl.ncarUsers.domain;
2.	import java.io.Serializable;

3.	public class Lab implements Serializable {
4.	    protected int ID;
5.	    protected String shortName;
6.	    protected String name;
7.	    protected String description;

8.	    ... //getters and setters   
9.	}
リスト 3. eedu.ucar.cisl.ncarUsers.domain.Division
1.	package edu.ucar.cisl.ncarUsers.domain;

2.	import java.io.Serializable;

3.	public class Division implements Serializable {
4.	    protected int ID;
5.	    protected String shortName;
6.	    protected String name;
7.	    protected String description;
8.	    protected int labID;

9.	    ... //getters and setters    
10.	}

データ・アクセス・レイヤーを実装する

データ・アクセス・レイヤー (DAL) に作成したデータ・アクセス・オブジェクトは、UserDAOLabDAODivisionDAO の 3 つです。データ・アクセス・オブジェクトは、ドメイン・オブジェクトに対応してもしなくても構いません。リスト 4 に UserDAO インターフェースを記載します。このインターフェースの実装を示すリスト 5 では、Spring JDBC フレームワークを使用して挿入/更新 (21 行目) とクエリー (30 行目) を実行しています。クエリーには内部クラスを実装しました (31 行目から 44行目)。これは、返された ResultSet オブジェクトを User オブジェクトにマッピングするためです。LabDAODivisionDAO も、これと同じように実装されています。

リスト 4. edu.ucar.cisl.ncarUsers.dal.UseDAO
1.    package edu.ucar.cisl.ncarUsers.dal;

2.    ...//imports

3.    public interface UserDAO
4.    {
5.      public User getUser(String s);    
6.      public void addUser(User user);
7.      public ArrayList<User> getAllUsers();
8.    }
リスト 5. edu.ucar.cisl.ncarUsers.dal.UserDAOJDBCImpl
1.   package edu.ucar.cisl.ncarUsers.dal;
2.   ...//imports

3.   public class UserDAOJDBCImpl extends SimpleJdbcDaoSupport 
          implements UserDAO {
4.        public getUser(String s) {
5.            String criteria="USERNAME = '" + s + "'";
6.            ArrayList<User> users=getUsers(criteria);
7.            if (users.size() > 0)
8.                return users.get(0);
9.            else
10.                return null;
11.        }

12.        public void addUser(User user) {
13.            Object objs[] = new Object[7];
14.            objs[0] = user.getUserName();
15.            objs[1] = user.getPassword();
16.            objs[2] = user.getEmail();
17.            objs[3] = user.getFirstName();
18.            objs[4] = user.getLastName();
19.            objs[5] = user.getLab();
20.            objs[6] = user.getDivision();

21.            this.getJdbcTemplate().update("insert into USER (USERNAME, 
                    PASSWORD, EMAIL, FIRST_NAME, LAST_NAME, LAB, DIVISION )
                     values (?, ?, ?, ?, ?, ?, ?)", objs);
22.            }

23.        public ArrayList<User> getAllUsers(){
24.            return getUsers(null);
25.        }

26.        protected ArrayList<User> getUsers(String criteria)   {
27.            String query="select ID, USERNAME, PASSWORD, EMAIL, 
                      FIRST_NAME, LAST_NAME, LAB, DIVISION from USER";
28.            if (criteria != null && criteria.trim().length() > 0)
29.                query= query + " Where " + criteria;
30.                Collection users = this.getJdbcTemplate().query(query,
31.                    new RowMapper() {
32.                        public Object mapRow(ResultSet rs, int rowNum) throws 
                                 SQLException {
33.                            User user = new User();
34.                            user.setID(rs.getInt("ID"));
35.                            user.setUserName(rs.getString("USERNAME"));
36.                            user.setPassword(rs.getString("PASSWORD"));
37.                            user.setEmail(rs.getString("EMAIL"));
38.                            user.setFirstName(rs.getString("FIRST_NAME"));
39.                            user.setLastName(rs.getString("LAST_NAME"));
40.                            user.setLab(rs.getInt("LAB"));
41.                            user.setDivision(rs.getInt("DIVISION"));
42.                            return user;
43.                         }
44.                     });
45.                ArrayList<User> results= new ArrayList <User>();
46.                Iterator it=users.iterator();
47.                while (it.hasNext())
48.                    results.add((User)it.next());

49.                return results;      
50.            }
51.       }

ビジネス・ロジック・レイヤーを実装する

ビジネス・ロジック・レイヤー (BLL) は、ビジネス・ルールが 1 つに集められる場所です。このレイヤーは、プレゼンテーション・レイヤーからのリクエストに対応し、DAL とやり取りを行い、バックエンドからデータを取得したり、DAL にデータの永続化を行うように要求したりします。この BLL には、合計 3 つのマネージャー・クラスを実装しました (ドメイン・オブジェクトごとに 1 つのマネージャー・クラス)。リスト 6 とリスト 7 に UserManager インターフェースとその実装をそれぞれ記載します。LabManager と DivisionManager の実装も、この UserManager の実装とほとんど同じです。

リスト 6. edu.ucar.cisl.ncarUsers.bll.UserManager
1.	package edu.ucar.cisl.ncarUsers.bll;

2.	...//imports

3.	public interface UserManager {
4.	    public User getUser(String userName);	
5.	    public void addUser(User user);	
6.	    public ArrayList<User> getAllUsers();
7.	}
リスト 7. edu.ucar.cisl.ncarUsers.bll.UserManagerImpl
1.	package edu.ucar.cisl.ncarUsers.bll;

2.	...//imports

3.	public class UserManagerImpl implements UserManager {
4.	    protected UserDAO userDao;

5.	    public User getUser(String userName)	{
6.	        return userDao.getUser(userName);
7.	    }

8.	    public void addUser(User user)	{
9.	        userDao.addUser(user);
10.	    }
  
11.	    public UserDAO getUserDao() {
12.	        return userDao;
13.	    }

14.	    public void setUserDao(UserDAO userDao) {
15.	        this.userDao = userDao;
16.	    }

17.	    public ArrayList<User> getAllUsers() {
18.	        return userDao.getAllUsers();
19.	    }	
20.	 }

プレゼンテーション・レイヤーを実装する

ブラウザー・リクエスト・ハンドラー

NCAR のアドミニストレーターが Web 上でユーザーを追加するには、ブラウザー・インターフェースが必要です。ここでは、ブラウザー・リクエスト・ハンドラー (BRH) を実装するために Spring MVC と Spring Web Flow フレームワークを使用します。Spring Web Flow に関する記事とチュートリアルはいくつも公開されていて、「参考文献」にもいくつか記載しているので参照してください。

リスト 8 で構成されているのは、Spring MVC サーブレットです。このサーブレットは、Ajax からのリクエストを除き、ブラウザーからのすべてのリクエストに対応します。適切なプラクティスとして、対象となるすべてのリクエストの URI は /brh で始まる一方、Ajax クライアントを含めた RESTful な Web サービス・クライアント・プログラムからのすべてのリクエストの URI は /rrh で始まります。

リスト 8. Spring MVC および Spring Web Flow を使用するための /Web-Inf/web.xml 内でのサーブレット定義
1.<servlet>
2.    <servlet-name>ncarUsers</servlet-name>
3.    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
4.    <init-param>
5.        <param-name>contextConfigLocation</param-name>
6.        <param-value>/WEB-INF/ncarUsers-spring-config.xml</param-value>
7.    </init-param>
8.</servlet>    
9.<servlet-mapping>
10.    <servlet-name>ncarUsers</servlet-name>
11.    <url-pattern>*.htm</url-pattern>
12.</servlet-mapping>

リスト 9 では、Spring Web Flow を構成しています。このリストではビュー名を JSP (JavaServer Pages) ファイルにマッピングし (6 行目から 9 行目)、フロー構成ファイルに定義されたフローを登録します (11 行目から 13 行目)。

リスト 9. /WEB-INF/ncarUsers-spring-config.xml
1.<?xml version="1.0" encoding="UTF-8"?>
2.<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:flow="http://www.springframework.org/schema/webflow-config"
xsi:schemaLocation="
      http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
      http://www.springframework.org/schema/webflow-config
      http://www.springframework.org/schema/webflow-config/spring-webflow-config-1.0.xsd">
3.    <bean name="/flow.htm" 
       class="org.springframework.webflow.executor.mvc.FlowController">
4.        <property name="flowExecutor" ref="flowExecutor"/>
5.    </bean>

6.    <bean id="viewResolver" 
       class="org.springframework.web.servlet.view.InternalResourceViewResolver">
7.        <property name="prefix" value="/WEB-INF/jsp/"/>
8.        <property name="suffix" value=".jsp"/>
9.    </bean>
10.    <flow:executor id="flowExecutor" registry-ref="flowRegistry"/>
11.    <flow:registry id="flowRegistry">
12.        <flow:location path="/WEB-INF/flows/**-flow.xml"/>
13.    </flow:registry>    
14.</beans>

Add User ページのフローを簡易化するために、フォーム・クラス (リスト 10) とフォーム・アクション・クラス (リスト 11) をそれぞれ 1 つ実装しました。フォーム・クラスには、ブラウザー・インターフェースに表示されるデータが含まれます。このクラスは、NCAR のアドミニストレーターが addUser.jsp (リスト 12) に示されているフォームに入力したデータを保存します。フォーム・データを HTML フォームのフィールドにバインドするためには、Spring タグ・ライブラリーを使用します。一方、フォーム・アクション・クラスには、フォーム・アクションに対する振る舞いが含まれます。

リスト 10. edu.ucar.cisl.ncarUsers.presentation.brh.AddUserForm
1.	package edu.ucar.cisl.ncarUsers.presentation.brh;

2.	...//imports

3.	public class AddUserForm implements Serializable {
4.	    protected ArrayList<Lab> labs;
5.	    protected User user;

6.	    public AddUserForm() {
7.	    }

8.	    ... //getters and setters

9.	}
リスト 11. edu.ucar.cisl.ncarUsers.presentation.brh.AddUserFormAction
1.	package edu.ucar.cisl.ncarUsers.presentation.brh;

2.	...//imports

3.	public class AddUserFormAction extends FormAction {
4.	    protected UserManager userManager;
5.	    protected LabManager labManager;

6.	    public AddUserFormAction() {
7.	        userManager = null;
8.	    }

9.	    public Event initForm(RequestContext context) throws Exception {
10.	        AddUserForm form = (AddUserForm) getFormObject(context);
11.	        form.setLabs(this.labManager.getLabs());
12.	        form.setUser(new User());
13.	        return success();
14.	    }

15.	    public Event submit(RequestContext context) throws Exception {
16.	        AddUserForm form = (AddUserForm) getFormObject(context);
17.	        User user = form.getUser();
18.	        userManager.addUser(user);
19.	        return success();
20.	    }

21.	    public Event addNewUser(RequestContext context) throws Exception {
22.	        initForm(context);
23.	        return success();
24.	    }

25.	    ... //getters and setters

26.	}
リスト 12. /WEB-INF/jsp/addUser.jsp
1.<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
2.<%@ page language="java"%>
3.<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
4.<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>

5.<html>
6.<head>
7.<title>NCAR New User Registration</title>
8.<script language="JavaScript" src="js/addUserAjax.js"></script>
9.</head>
10.<body>
11.<h1>NCAR New User Registration</h1>
12.<form:form commandName="addUserForm" method="post" 
          action="flow.htm">
13.<input type="hidden" name="_flowExecutionKey" 
          value="${flowExecutionKey}">
14.<table>
15.<tr>
16.    <td>Username:</td>
17.    <td align="left">
18.        <form:input path="user.userName" id="userName" 
                     onblur="validateUsername();" />
19.   </td>
20.</tr>
21.<tr>
22.    <td>Password:</td>
23.    <td align="left">
24.        <form:input path="user.password" id="password" />
25.    </td>
26.</tr>
27.<tr>
28.     <td>&nbsp;</td>
29.</tr>
30.<tr>
31.    <td>First Name:</td>
32.    <td align="left">
33.        <form:input path="user.firstName" id="email" />
34.    </td>
35.</tr>
36.<tr>
37.    <td>Last Name:</td>
38.    <td align="left">
39.        <form:input path="user.lastName" id="email" />
40.    </td>
41.</tr>
42.<tr>
43.    <td>Email:</td>
44.    <td align="left">
45.        <form:input path="user.email" id="email" />
46.    </td>
47.</tr>
48.<tr>
49.    <td>Lab:</td>
50.    <td align="left">
51.        <form:select id="lab" path="user.lab" onclick="updateDivisions();">
52.            <form:option value="0" label="--Please Select--" />
53.            <form:options items="${addUserForm.labs}" itemValue="ID" 
                        itemLabel="name" />
54.        </form:select>
55.    </td>
56.</tr>
57.<tr>
58.    <td>Division:</td>
59.    <td align="left">
60.        <form:select id="division" path="user.division" disabled="true">
61.        </form:select>
62.    </td>
63.</tr>
64.<tr>
65.    <td>&nbsp;</td>
66.</tr>
67.<tr>
68.    <td colspan="2" align="center">
69.        <input type="submit" name="_eventId_submit" value="Submit">
70.    </td>
71.</tr>
72.</table>
73.</form:form>
74.</body>
75.</html>

リスト 13 に、フォーム・アクション・クラス AddUserFormAction の構成を記載します。このクラスが使用するのはフォーム・クラス (3 行目)、そして BLL の userManager および labManager クラス (6、7 行目) です。この両方のマネージャー・クラスを構成する Spring 構成ファイル applicationContext.xml については後で説明します。

リスト 13. /WEB-INF/flows/addUser-beans.xml
1.  <beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
2.      <bean id="addUserFormAction" 
                class="edu.ucar.cisl.ncarUsers.presentation.brh.AddUserFormAction">
3.          <property name="formObjectName" value="addUserForm"/>
4.          <property name="formObjectClass" 
                    value="edu.ucar.cisl.ncarUsers.presentation.brh.AddUserForm"/>
5.          <property name="formObjectScope" value="FLOW"/>
6.          <property name="userManager" ref="userManager"/>  
7.          <property name="labManager" ref="labManager"/>          
8.      </bean>    
9.  </beans>

リスト 14 に、フロー内での状態と、状態を遷移させるアクションを定義します。

リスト 14. /WEB-INF/flows/addUser-flow.xml
1.	<?xml version="1.0" encoding="UTF-8"?>
2.	<flow xmlns=http://www.springframework.org/schema/webflow
         xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
              xsi:schemaLocation="http://www.springframework.org/schema/webflow
         http://www.springframework.org/schema/webflow/spring-webflow-1.0.xsd">

3.	    <start-state idref="addUser"/>

4.	    <view-state id="addUser" view="addUser">
5.	        <render-actions>
6.	            <action bean="addUserFormAction" method="initForm"/>
7.	        </render-actions>
8.	        <transition on="submit" to="submit">
9.	            <action bean="addUserFormAction" method="bind"/>
    </transition>
10.	    </view-state>

11.	    <view-state id="summary" view="summary">
12.	        <transition on="addNewUser" to="addNewUser" />
13.	    </view-state>

14.	    <action-state id="submit">
15.	        <action bean="addUserFormAction" method="submit"/>
    <transition on="success" to="summary"/>
16.	    </action-state>

17.	    <action-state id="addNewUser">
18.	        <action bean="addUserFormAction" method="addNewUser"/>
19.	        <transition on="success" to="addUser"/>
20.	    </action-state>    

21.	    <import resource="addUser-beans.xml"/>    

22.	</flow>

リソース・リクエスト・ハンドラー

リソース・クラスは、RESTful な Web サービスとしてクライアント・アプリケーションに公開する内容を決定します。リソース・リクエスト・ハンドラー (RRH) での RESTful な Web サービスの実装を容易にするのは、Jersey です。Jersey は、リソース・クラスと URI とのマッピング、そして HTTP リクエストに含まれる標準的な HTTP メソッドとリソース・クラスのメソッドとのマッピングにアノテーションを使用します。Jersey を使うには、web.xml ファイルに特殊なサーブレットを構成する必要があります (リスト 15)。このサーブレットは初期化されると edu.ucar.cisl.ncarUsers.presentation.rrh パッケージ内のクラスを巡回してすべてのリソース・クラスを見つけ、これらのクラスをアノテーションが付けられた URI にマッピングします。RESTful な Web サービスに対するすべてのリクエストの URI は /rrh で始まり、これらのリクエストはサーブレットによって処理されます。

リスト 15. Jersey を使用するための /Web-Inf/web.xml 内でのサーブレット定義
1.  <servlet>
2.    <servlet-name>rrh</servlet-name>
3.    <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
4.    <init-param>
5.        <param-name>com.sun.jersey.config.property.packages</param-name>
6.        <param-value>edu.ucar.cisl.ncarUsers.presentation.rrh</param-value>
7.    </init-param>
8.  </servlet>
9.  <servlet-mapping>
10.    <servlet-name>rrh</servlet-name>
11.    <url-pattern>/rrh/*</url-pattern>
12.  </servlet-mapping>

実装されているリソース・クラスは、UsersResource (リスト 16)、UserResource (リスト 17)、DivisionResource (リスト 18) の 3 つです。リスト 16 では、クラス定義の前にある 3 行目の @Path アノテーションによって、UsersResource クラスが /users という URI にマッピングされます。getUsersAsJsonArray メソッドの前にある 21 行目の @GET と 22 行目の @Produces(application/json) の 2 つのアノテーションは、このメソッドが HTTP GET リクエストを処理し、そのレスポンスのコンテンツ・タイプが JSON であることを示しています。putUsers メソッドの前にある 41 行目の @PUT と 42 行目の @Consumes("text/plain") は、HTTP PUT リクエストを処理し、入力 HTTP 本体に期待されるコンテンツ・タイプがプレーン・テキストであることを意味します。この例の場合、ファイルは 1 行につき 1 人のユーザーという形式のフラット・ファイルです。各ユーザーの属性は、垂直線 (|) で区切られます。

リスト 16. edu.ucar.cisl.ncarUsers.presentation.rrh.UsersResource
1.  package edu.ucar.cisl.ncarUsers.presentation.rrh;

2.  ...//imports

3.  @Path("/users/")
4.  public class UsersResource {
5.     protected @Context UriInfo uriInfo;
6.     protected UserManager userManager;
7.     protected DivisionManager divisionManager;
8.     protected LabManager labManager;

9.     public UsersResource() {
10.        userManager = (UserManager)
11.          BeanFactory.getInstance().getBean("userManager");
12.        divisionManager = (DivisionManager)
13.          BeanFactory.getInstance().getBean("divisionManager");
14.        labManager = (LabManager)
15.          BeanFactory.getInstance().getBean("labManager");
16.  }

17.     @Path("{username}/")
18.     public UserResource getUser(@PathParam("username") String userName) {
19.        return new UserResource(uriInfo, userManager, userName);
20.     }

21.     @GET
22.     @Produces("application/json")
23.     public JSONArray getUsersAsJsonArray() throws JSONException {
24.        ArrayList<User> users = this.userManager.getAllUsers();
25.        JSONArray usersArray = new JSONArray();
26.        for (User user : users) {
27.          JSONObject obj = new JSONObject();
28.          obj.put("USERNAME", user.getUserName());
29.          obj.put("PASSWORD", user.getPassword());
30.          obj.put("FIRST_NAME", user.getFirstName());
31.          obj.put("LAST_NAME", user.getLastName());
32.          obj.put("EMAIL", user.getEmail());
33.          String labName=labManager.getLabName(user.getLab());
34.          obj.put("LAB", labName);
35.          String divisionName= divisionManager.getDivisionName(user.getDivision());
36.          obj.put("DIVISION", divisionName);
37.          usersArray.put(obj);
38.        }
39.        return usersArray;
40.     }

41.     @PUT
42.     @Consumes("text/plain")
43.     public Response putUsers(String input) throws IOException {
44.        Reader reader=new StringReader(input);
45.        BufferedReader br=new BufferedReader(reader);
46.        while (true) {
47.          String line=br.readLine();
48.          if (line == null)
49.             break;
50.          processUser(line);      
51.        }    
52.        return Response.created(uriInfo.getAbsolutePath()).build();
53.  }

54.  /********************************
If the user exists, update it. Otherwise, create a new one
@param input
   */
55.     protected void processUser(String input)
56.     {
57.        StringTokenizer token=new StringTokenizer(input, "|");
58.        String userName=token.nextToken();
59.        String password=token.nextToken();
60.        String firstName=token.nextToken();
61.        String lastName=token.nextToken();
62.        String email=token.nextToken();
63.        String labName=token.nextToken();
64.        String divisionName=token.nextToken();

65.        int lab=this.labManager.getLabID(labName);
66.        int division=this.divisionManager.getDivisionID(divisionName);
67.        User user=this.userManager.getUser(userName);
68.        if (user == null)
69.          user=new User();

70.        user.setUserName(userName);
71.        user.setPassword(password);
72.        user.setFirstName(firstName);
73.        user.setLastName(lastName);
74.        user.setEmail(email);
75.        user.setLab(lab);
76.        user.setDivision(division);

77.        this.userManager.addUser(user);    
78.     }
79.  }

上記のリスト 16 で、getUser メソッドの前にある 17 行目の @Path("{username}/") アノテーションは、/users/ と次のスラッシュの間にあるストリングを示しています。リクエストの URI にこのストリングが含まれている場合には、これを username 変数の値として使用し、getUser メソッドによって子リソース・クラス UserResource のインスタンスを返します。すると Jersey は対応する HTTP メソッドのアノテーションが付いた UserResource クラス内のメソッドを呼び出します。リスト 17 では、Jersey が HTTP リクエストの GET メソッドに対して @GET@Produces("application/json") のアノテーションが付いた getUser メソッドを呼び出し、JSON フォーマットでユーザー・データを返します (12 行目から 19 行目)。

リスト 17. edu.ucar.cisl.ncarUsers.presentation.rrh.UserResource
1. package edu.ucar.cisl.ncarUsers.presentation.rrh;

2. ...//imports

3. public class UserResource {
4.     protected String userName;
5.     protected UriInfo uriInfo;
6.     protected UserManager userManager;

7.     public UserResource(UriInfo uriInfo, UserManager userManager, String userName) {
8.         this.uriInfo = uriInfo;
9.         this.userName = userName;
10.         this.userManager = userManager;
11. }

12.     @GET
13.     @Produces("application/json")
14.     public JSONObject getUser() throws JSONException {
15.         JSONObject obj = new JSONObject();
16.         User user = this.userManager.getUser(userName);
17.         if (user != null) 
18.             obj.put("userName", user.getUserName()).put("email", user.getEmail());
           return obj;
19.     }
20. }

リスト 18 のリソース・クラス DivisionsResource は、上記の UsersResource と非常に似た方法で実装されています。3 行目では、このクラスには URI パス /divisions/ を設定したアノテーションが付けられています。@GET および @Produces のアノテーションが付いた getDivisions メソッドは、部門の JSON 配列を返します (行 10 から 23)。

リスト 18. edu.ucar.cisl.ncarUsers.presentation.rrh.DivisionsResource
1.   package edu.ucar.cisl.ncarUsers.presentation.rrh;

2.   ...//imports

3.   @Path("/divisions/")

4.   public class DivisionsResource {
5.       protected DivisionManager divisionManager;

6.       public DivisionsResource() {
7.           divisionManager = (DivisionManager)
8.               BeanFactory.getInstance().getBean("divisionManager");
9.       }

10.       @GET
11.       @Produces("application/json")
12.       public JSONArray getDivisions(@QueryParam("labID") String labID) 
13.           throws JSONException {
14.           int id = Integer.parseInt(labID);
15.           ArrayList<Division> divisions = this.divisionManager.getDivisions(id);
16.           JSONArray divisionsArray = new JSONArray();
17.           for (Division division : divisions) {
18.               JSONObject obj = new JSONObject();
19.               obj.put("ID", division.getID()).put("name", division.getName());
20.               divisionsArray.put(obj);
21.           }
22.           return divisionsArray;
23.       }
24.   }

クライアント・アプリケーション

Ajax

Ajax は RESTFul な Web サービスに対して、クライアントとして機能します。この 2 つが連動することによって、デスクトップのようなリッチで応答性に優れたブラウザー・インターフェースを作り出すことができます。サンプル・アプリケーションでは、Ajax を 2 箇所で使いました。その 2 箇所とは、データベースにユーザー名がすでに存在しているかどうかを調べる箇所 (リスト 12 の 18 行目) と、指定されたラボの部門リストを非同期にリクエストして、ページ全体を更新することなく部門の選択メニューだけを更新する箇所 (リスト 12 の 51 行目) です。

リスト 19 に記載した JavaScript では、2 行目から 13 行目の validateUsername 関数が XMLHttpRequest をセットアップして RESTful な Web サービスに送信し、指定されたユーザー名のユーザー・データをブラウザーに取り込みます。14 行目から 27 行目の usernameCallback 関数は、RESTful な Web サービスを実行しているサーバーからのレスポンスを処理するコールバック関数です。レスポンスにユーザー・データが含まれる場合、その指定されたユーザー名のユーザーはすでに存在していることを示します。その場合、警告メッセージが表示され、ブラウザーの「Username (ユーザー名)」フィールドがクリアされます。

28 行目から 39 行目の updateDivisions 関数は、NCAR のアドミニストレーターがラボの選択メニューで選択したラボの部門を取得するために、RESTful な Web サービスにリクエストを送信します。40 行目から 55 行目のコールバック関数 updateDivisionsCallback が、レスポンスを処理し、返された部門名を部門選択メニューに表示します。

リスト 19. js/addUserAjax.js
1.    var req;
2.    function validateUsername() {
3.        var username = document.getElementById("userName");
4.        var url = "rrh/users/" + escape(username.value);
5.        if (window.XMLHttpRequest) {
6.            req = new XMLHttpRequest();
7.        } else if (window.ActiveXObject) {
8.            req = new ActiveXObject("Microsoft.XMLHTTP");
9.        }

10.        req.open("Get", url, true);
11.        req.onreadystatechange = usernameCallback;
12.        req.send(null);
13.    }

14.    function usernameCallback() {
15.        if (req.readyState == 4 && if (req.status == 200) {
16.            var jsonData = req.responseText;
17.            var myJSONObject = eval("(" + jsonData + ")");
18.            var un = myJSONObject.userName;
19.            var username = document.getElementById("userName");
20.            if (username.value == un) {
21.                alert("Warning: " + username.value + 
22.                   " exists already. Choose another username.");
23.                username.value = "";
24.                username.focus();
25.            }
26.        }
27.    }

28.    function updateDivisions() {
29.        var labSel = document.getElementById("lab");
30.        var url = "rrh/divisions/?labID=" + escape(labSel.value);
31.        if (window.XMLHttpRequest) {
32.            req = new XMLHttpRequest();
33.        } else if (window.ActiveXObject) {
34.            req = new ActiveXObject("Microsoft.XMLHTTP");
35.        }
36.        req.open("Get", url, true);
37.        req.onreadystatechange = updateDivisionsCallback;
38.        req.send(null);
39.    }

40.    function updateDivisionsCallback() {
41.        if (req.readyState == 4)&& req.status == 200) {
42.            var jsonData = req.responseText;
43.            var divisionsData = eval("(" + jsonData + ")");
44.            var divisionSel = document.getElementById("division");
45.            var length = divisionSel.length;
46.            for (var b = 0; b < length; b++) {
47.                divisionSel.options[b] = null;
48.            }
49.            for (var a = 0; a < divisionsData.length; a++) {
50.                divisionSel.options[a] = new 
51.                   Option(divisionsData[a].name, divisionsData[a].ID);
52.            }
53.            divisionSel.disabled = "";
54.        }
55.    }

Ruby スクリプト

RESTful な Web サービスのクライアントは、Perl、Ruby、Python、C、C#、または Java コードなどの言語で容易に実装することができます。この記事では、その一例として Ruby を使用します。リスト 20 に記載する Ruby スクリプトは、RESTful な Web サービスからユーザーのデータをダウンロードし、各ユーザー・データを垂直線 (|) で区切られた属性と併せて保存します。リスト 21 の Ruby スクリプトは、ユーザーのデータをファイルからサーバーにアップロードします。

リスト 20. client/downloadUsersData.rb
54. #!/usr/bin/ruby

55. require 'rubygems'
56. require 'json'
57. require 'open-uri'
58. $KCODE = 'UTF8'

59. def download(filename)
60. file=File.new(filename, 'w')
61. base_uri = 'http://localhost:8080/ncarUsers/rrh/users/'

62. # Make the HTTP request and read the response entity-body as a JSON
63. # document.
64. json = open(base_uri).read

65. # Parse the JSON document into a Ruby data structure.
66. json = JSON.parse(json)

67. # Iterate over the data structure...
68. json.each { |r| file.puts r['USERNAME'] + '|' + r['PASSWORD'] + '|' +
                    r['FIRST_NAME'] +  '|' +  r['LAST_NAME'] + 
69. '|' + r['EMAIL'] + '|' +  r['LAB'] + '|' + r['DIVISION']; }
70. end

71. # Main program.
72. unless ARGV[0]
73. puts "Usage: #{$0} [file name]"
74. exit
75. end
76. download(ARGV[0])
リスト 21. client/uploadUsersData.rb
1. #!/usr/bin/ruby
2. require 'rubygems'
3. require 'rest-open-uri'
4. require 'uri'
5. require 'cgi'


6. def uploadUsers(content)
7. base_uri = 'http://localhost:8080/ncarUsers/rrh/users'
8. begin
9. response = open(base_uri, :method => :put, 'Content-Type' => 
                      "text/plain", :body => content)
10. rescue OpenURI::HTTPError => e
11. response_code = e.io.status[0].to_i
12. puts response_code 
13. if response_code !=  "200" 
a. puts "Sorry, Can't post the users"
14. else
a. raise e
15. end
16. end

17. end

18. def upload(filename)
19. File.open(filename) do |file|
20. content = file.read
21. uploadUsers(content)
22. end
23. end


24. # Main program.
25. unless ARGV[0]
26. puts "Usage: #{$0} [file name]"
27. exit
28. end
29. upload(ARGV[0])

コンポーネントを結合する

データ・アクセス・レイヤー、ビジネス・ロジック・レイヤー、そしてプレゼンテーション・レイヤーそれぞれのコンポーネントを結合するには、Spring フレームワークを使用します。Spring フレームワークは IoC (Inversion of Control: 制御の反転) によってコンポーネント依存関係の作成と管理を外部化します。リスト 22 に、コンポーネントとその依存関係を定義する Spring 構成ファイルを記載します。このリストでは、データ・ストアとトランザクション・マネージャーも構成しています。

リスト 22. applicationContext.xml
1.    <beans xmlns=http://www.springframework.org/schema/beans
2.        xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
3.        xmlns:tx=http://www.springframework.org/schema/tx 
4.        xsi:schemaLocation="
5.           http://www.springframework.org/schema/beans
6.    http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
7.    http://www.springframework.org/schema/tx
8.    http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">

9.        <tx:annotation-driven/>
10.        <bean id="dataSource"
11.            class="org.springframework.jdbc.datasource.DriverManagerDataSource">
12.            <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
13.            <property name="url" value="jdbc:mysql://localhost:3306/ncar_users"/>
14.            <property name="username" value="tutorial"/>
15.            <property name="password" value="tutorial"/>
16.        </bean>

17.        <bean id="transactionManager" 
18.              class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
19.            <property name="dataSource" ref="dataSource"/>
20.        </bean>    
21.        <bean id="userManager"
22.            class="edu.ucar.cisl.ncarUsers.bll.UserManagerImpl">
23.            <property name="userDao"><ref local="userDao"/></property>
24.        </bean>
25.        <bean id="labManager"
26.            class="edu.ucar.cisl.ncarUsers.bll.LabManagerImpl">
27.            <property name="labDao"><ref local="labDao"/></property>
28.        </bean>
29.        <bean id="divisionManager"
30.            class="edu.ucar.cisl.ncarUsers.bll.DivisionManagerImpl">
31.            <property name="divisionDao"><ref local="divisionDao"/></property>
32.        </bean>    
33.        <bean id="userDao" class="edu.ucar.cisl.ncarUsers.dal.UserDAOJDBCImpl">
34.            <property name="dataSource"><ref local="dataSource"/></property>
35.        </bean>     
36.        <bean id="labDao" class="edu.ucar.cisl.ncarUsers.dal.LabDAOJDBCImpl">
37.            <property name="dataSource"><ref local="dataSource"/></property>
38.        </bean>     
39.        <bean id="divisionDao"
40.            class="edu.ucar.cisl.ncarUsers.dal.DivisionDAOJDBCImpl">
41.            <property name="dataSource"><ref local="dataSource"/></property>
42.        </bean>       
43.    </beans>

この Web アプリケーションが起動されると、web.xml ファイルに構成されたコンテキスト・ローダー・リスナーが上記の applicationContext.xml ファイルをロードします (リスト 23)。

リスト 23. applicationContext.xml をロードするコンテキスト・ローダー・リスナーの web.xml 内での構成
1. <context-param>
2.     <param-name>contextConfigLocation</param-name>
3.     <param-value>/WEB-INF/classes/applicationContext.xml</param-value>
4. </context-param>

5. <listener>
6.     <listener-class>
              org.springframework.web.context.ContextLoaderListener</listener-class>
7. </listener>

Eclipse からアプリケーションを実行する

このサンプル・アプリケーションを Eclipse から実行する手順は以下のとおりです。

  1. 「Project Explorer (プロジェクト・エクスプローラー)」で、「ncarUsers」プロジェクトを右クリックし、「Run As (実行)」 > 「Run On Server (サーバーで実行)」の順に選択するか、デバッグ・モードで実行する場合は「Debug As (デバッグ)」 > 「Debug On Server (サーバーでデバッグ)」の順に選択します。
  2. localhost」 > 「Tomcat v6.0 Server at localhost (ローカル・ホストの Tomcat v6.0 サーバー)」の順に選択します。
    図 12. Eclipse 内での Tomcat の実行
    Eclipse 内での Tomcat の実行
  3. Finish (完了)」をクリックします。すると「Servers (サーバー)」タブが開き、サンプル・アプリケーションが Tomcat にデプロイされていること、そしてサーバーが実行中であることが示されます。「Console (コンソール)」タブに切り替えると、Tomcat サーバーが生成したメッセージを確認することができます。
  4. ブラウザーを開いて、http://localhost:8080/ncarUsers にナビゲートし、「Sign Up New Users App (新規ユーザー登録アプリケーション)」リンクをクリックしてください。
    図 13. 「NCAR New User Registration (NCAR 新規ユーザー登録)」ページのブラウザー・インターフェース
    「NCAR New User Registration (NCAR 新規ユーザー登録)」ページのブラウザー・インターフェース

    ご覧のように、「Lab (ラボ)」の選択メニューには「--Please Select-- (--選択してください--)」と表示されていて、「Division (部門)」の選択メニューは無効になっています。各テキスト・フィールドに何らかのデータを入力して、「Lab (ラボ)」を選択してみてください。すると「Division (部門)」の選択メニューに、選択されたラボの部門が表示されるようになるので、そこから部門を選択します。

  5. Submit (送信)」をクリックします。すると、新規ユーザーが作成され、「Signup Confirmation (登録確認)」ページが表示されます。
    図 14. 「NCAR New User Registration (NCAR 新規ユーザー登録)」での「Signup Confirmation (登録確認)」ページ
    「NCAR New User Registration (NCAR 新規ユーザー登録)」での「Signup Confirmation (登録確認)」ページ
  6. Add New User (新規ユーザーの追加)」をクリックします。
  7. NCAR New User Registration (NCAR 新規ユーザー登録)」アプリケーションのトップ・ページで、「Username (ユーザー名)」フィールドに前に入力したユーザー名と同じユーザー名を入力してから、次のフィールドに移動してみてください。入力したユーザー名が既に使用されていることを警告するポップアップ・ウィンドウが表示され、「Username (ユーザー名)」フィールドがクリアされるはずです。
  8. 何人かのユーザーを作成した後、コマンド・プロンプトを開いてください。downloadUsers.rb Ruby スクリプトを実行して、ユーザーのデータをダウンロードします (図 15)。このダウンロード・ファイルは、複数の新規ユーザーを追加するためのテンプレートとして使用することができます。新規ユーザーをアプリケーション・サーバーにアップロードするには、uploadUsers.rb を使用します。
    図 15. downloadUsers.rb スクリプトの実行によるユーザーのデータのダウンロード
    downloadUsers.rb スクリプトの実行によるユーザーのデータのダウンロード

舞台裏での動作

ユーザーが「Sign Up New User (新規ユーザー登録)」アプリケーションを開くと、プレゼンテーション・レイヤーの BRH が Web インターフェースからのリクエストに対応します。ユーザーがすべてのフィールドにデータを入力して送信すると、BRH の AddUserFormAction オブジェクトが Submit リクエストを受け入れます。図 16 に示されているように、このオブジェクトは必要なデータを BLL に渡すと、BLL がデータ・アクセス・レイヤーに対し、そのデータを MySQL データベースに保存するように要求します。これに続いて、確認ページがブラウザーに表示されます。

図 16. 新規ユーザー追加のシーケンス図
新規ユーザー追加のシーケンス図

ユーザー名が入力されると、Ajax スクリプトが RESTful な Web サービスを呼び出します。この場合にリクエストを処理するのは、RRH レイヤーにある UsersResource および UserResource オブジェクトです。指定されたユーザー名がすでにデータベース内に存在する場合には、JSON データ構造がブラウザーに返されます。すると Ajax スクリプトが警告メッセージを表示して、「Username (ユーザー名)」フィールドをクリアします。

ユーザーが「Lab (ラボ)」の選択メニュー・フィールドから選択すると、Ajax スクリプトは RRH の DivisionsResource で GET Web サービスを呼び出し、この呼び出しによって、選択されたラボの部門の配列が返されます。図 17 に示すのは、指定されたラボの部門情報を取得して「Division (部門)」の選択メニューに表示するために、Ajax スクリプトが RESTful な Web サービスへのリクエストを送信した後の各ティアの間でのリクエストのシーケンスです。

図 17. 部門情報を取得するためのシーケンス図
部門情報を取得するためのシーケンス図

ユーザーのデータのアップロードおよびダウンロードに使用される Ruby スクリプトは、RESTful な Web サービスのクライアントでもあります。Ruby スクリプト downloadUsers.rb によって、サーバーに HTTP GET リクエストが送信された場合、JSON フォーマットでユーザーのデータが返されます。一方、uploadUsers.rb はサーバーに対し、HTTP 本体にユーザーのデータが含まれる HTTP PUT リクエストを送信します。この場合のデータ・フォーマットは 1 行につき一人のユーザーという形式です。各行には、それぞれのユーザーの属性が垂直線 (|) で区切られた形で含まれます。

BRH と同じく、RRH もインターフェースを提供しますが、その対象は BRH とは異なるクライアントで、ビジネス・ロジック・レイヤーに対して処理を行うようにリクエストします。このリクエストによって、今度はビジネス・ロジック・レイヤーがデータ・アクセス・レイヤーに対し、データ永続化の処理を行うように要求します。


まとめ

最近の Web アプリケーションには、リッチなインターフェースだけでなく、クライアントがプロセスを自動化できるようにするために RESTful な Web サービスを提供するよう求められることがますます多くなってきています。この記事では、記事「RESTful な Web サービスを構築するためのマルチティア・アーキテクチャー」(「参考文献」を参照) で説明したマルチティア・アーキテクチャーを使用して、動的な Web アプリケーションと RESTful な Web サービスの両方を構築する方法を説明しました。また、Ajax と RESTful な Web サービスが連動して、デスクトップのようなリッチで応答性に優れたインターフェースが作成される仕組みについても説明しました。この記事では、Eclipse、Jersey、Spring MVC フレームワーク、Spring Web Flow、Spring JBDC フレームワーク、MySQL を使用しています。RESTful な Web サービスのクライアントとして使用しているのは、Ruby スクリプトです。

なお、この記事は、University Corporation for Atmospheric Research (米国大気研究大学機構) との協力協定に基づいて National Science Foundation (全米科学財団) が一部をサポートする研究によって実現したものです。National Center for Atmospheric Research は National Science Foundation の後援を受けています。最後に、この記事でのサンプル・アプリケーションの選択と記事の編集に関して助言をしていただいた NCAR の Markus Stobbs 氏に感謝いたします。


ダウンロード

内容ファイル名サイズ
Source codencarUsers.zip5987KB

参考文献

学ぶために

  • Rod Johnson が記事「Introduction to the Spring Framework」で、Spring が達成しようとしている目標、そしてそれが Java EE アプリケーションの開発にどのように役立つかを説明しています。
  • 関連記事「RESTful な Web サービスを構築するためのマルチティア・アーキテクチャー」(developerWorks、2009年6月) を読んで、REST および RESTful な Web サービスの概念について、その概要を理解してください。この記事では、RESTful な Web サービスと動的な Web アプリケーションの両方を構築するためのマルチティア・アーキテクチャーについて説明しています。
  • チュートリアル「Spring DAO with JBDC」で、JDBC でデータベースにアクセスするデザイン・パターン、Data Access Objects (DAO) の実装方法を解説しています。さらにこのチュートリアルでは、IoC を使用してコード品質を改善する方法についても学べます。
  • The Complete Spring Tutorial」では、Web アプリケーションで Struts、Spring、および Hibernate を統合する方法を説明しています。
  • Spring Web Flow」は、Spring Web Flow と Spring Framework を使用して Web アプリケーションを構築する際の入門書となります。
  • Introduction to RESTful Web Services and Jersey」で、REST アーキテクチャー、RESTful な Web サービス、そして Jersey と呼ばれる Sun の JAX-RS リファレンス実装について説明しています。
  • Implementing RESTful Web Services in Java」では、Jersey 仕様に準拠した RESTful な Web サービスを Java で作成する方法を説明しています。

製品や技術を入手するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development, SOA and web services
ArticleID=416812
ArticleTitle=マルチティア・アーキテクチャーで RESTful な Web サービスと動的な Web アプリケーションを構築する
publish-date=06302009