ひがやすを技術ブログ

電通国際情報サービスのプログラマ

Agile Java EE Beyond Rails

EJB3JSFJPAというJava EEの標準仕様をベースに、その開発効率をRails以上に高めようという新プロジェクトchuraを立ち上げます。churaの基本構成は、Seasar2.4 + Teeda + KuinaDao + S2Hibernate-JPA + S2Dxo + ツール群という形になります。S2Hibernate-JPAは、最終的には、Kuinaで置き換えるので、すべてのプロダクトがSeasarファウンデーションから提供されることになります。
churaのコンセプトは、

Agile Java EE Beyond Rails

Java EEの標準仕様をベースにして、Railsを超える生産性を発揮するように拡張機能を追加します。
Java EEの標準仕様は重要。でも素のJava EEの開発は、ファイルをパッケージング化したりアプリケーションサーバのリブートがたびたび必要だったり、重くてもううんざり。Railsなみにさくさく開発したいという人に是非使って欲しい。もちろん、Java EEの標準仕様なんてどうでもいい。ただAgileに開発したいだけなんだという人も是非使ってください。あるいは、とにかく工数を削減したいという方にも向いています。
Seasar2フレームワーク群をAllInOneにして欲しいという要望にもこれでお答えできます。
EJB3JSFJPAをどのように組み合わせるのがいいのだろうかと、悩まれている方も多いのではないでしょうか。この辺のアーキテクチャは、昨日、一昨日とblogに書きました。churaは、Type5のアーキテクチャにもとづいています。単なるAllInOneパッケージではなく、統一されたアーキテクチャにもとづいているのです。
churaのツール群には、ソースコードの自動生成やDBの移行(リファクタリング)が含まれます。要望があれば、随時追加したいと思います。

サンプルアプリ

churaのイメージをつかんでもらうために、単純なサンプルを元に説明します。まだ、実装されていないところもありますが、イメージということで。
このサンプルは、従業員管理のアプリケーションです。S2JSF-exampleに出てくるやつの簡易版です。テーブルは次の2つです。


CREATE TABLE department (
id NUMERIC(9) NOT NULL PRIMARY KEY,
name VARCHAR(20)
);
CREATE TABLE employee (
id NUMERIC(9) NOT NULL PRIMARY KEY,
name VARCHAR(20),
deparment_id NUMERIC(9),
CONSTRAINT fk_department_id FOREIGN KEY (department_id)
REFERENCES department(id)
);
このスキーマ情報を元に次のEntity、Dao、EntityLogicの雛型を自動生成します。

@Entity
public class Department {
@Id(generate=GeneratorType.AUTO)
private int id;
private String name;
...
}
@Entity
public class Employee {
@Id(generate=GeneratorType.AUTO)
private int id;
private String name;
@ManyToOne
private Department department;
...
}

public interface DepartmentDao {
List findAll();
Department find(int id);
void persist(Department department);
void remove(Department department);
boolean contains(Department department);
Employee merge(Department department);
void refresh(Department department);
void readLock(Department department);
void writeLock(Department department);
}
public interface EmployeeDao {
List findAll();
Employee find(int id);
void persist(Employee employee);
void remove(Employee employee);
boolean contains(Employee employee);
Employee merge(Employee employee);
void refresh(Employee employee);
void readLock(Employee employee);
void writeLock(Employee employee);
}
Daoの実装は、AOPで自動生成されるので、必要ありません。

public interface DepartmentLogic {
List findAll();
Department find(int id);
void persist(Department department);
void remove(Department department);
}
public interface EmployeeLogic {
List findAll();
Employee find(int id);
void persist(Employee employee);
void remove(Employee employee);
}
EntityLogicについては、実装クラスの雛型も作成されます。

public class DepartmentLogicImpl implements DepartmentLogic {
private DepartmentDao dao;
...
List findAll() {
return dao.findAll();
}
...
}
public class EmployeeLogicImpl implements EmployeeLogic {
private EmployeeDao dao;
...
List findAll() {
return dao.findAll();
}
...
}

サンプルアプリ2

このアプリケーションは、4つの画面で構成されます。

  • 検索条件入力画面
  • 検索結果一覧画面
  • 編集画面
  • 確認画面

最初は、検索条件入力画面です。従業員名か部署を指定することができます。部署はリストから選択します。HTML(employeeSearch.html)は次のようになります。重要でないところは省略してます。


<form id="employeeSearchForm">
従業員名:<input id="name" type="text"/>

部署 :<select id="departmentId" size="2">
<option>Please select
<option value="1">Accounting
<option value="2">Research
</select>

<input id="doFindEmployee" type="submit" value="find"/>
</form>
関連するコードを自動生成しましょう。employeeSearch.htmlを右クリックして、コード自動生成を選びます。出てくるダイアログで幾つかオプションを指定します。

  • 関連するEntityには、Employeeを指定します。
  • 自動生成タイプには、検索条件入力画面を指定します。
    • この自動生成タイプは、ユーザが追加することもカスタマイズすることもできます。

自動生成のボタンをクリックしてソースを作成します。できたソースを見てみましょう。

EmployeeSearchPage.java

HTMLのファイル名の最初を大文字にし、Pageをsuffixにつけたクラスが自動生成されます。ソースは次のようになります。Java5と1.4の環境では、吐き出されるソースコードが異なります。今回は、Java5の例です。

public class EmployeeSearchPage {
//@Navigation(to=)
public static final String SUCCESS = "success";

private String name;
//@SelectOneMenu(label="*Name")
private int departmentId;
private List departmentIdItems;
private DepartmentLogic departmentLogic;
private EmployeeSearchDxo dxo;

...
public String initialize() {
//TODO
departmentIdItems = departmentLogic.findAll();
return null;
}

public String doFindEmployee() {
//TODO
return SUCCESS;
}
}

nameプロパティは、HTMLのid属性から来ています。
departmentIdプロパティは、HTMLのid属性から来ています。なぜ、型がintだと分かるのでしょうか。ソースコードを自動生成するときに、このページのベースEntityはEmployeeだと指定しています。Employeeには、departmentプロパティがあり、その型はDepartementです。さらにDepartmentにはidというプロパティがあり、型はintです。これらの情報によりツールが適切な型を推測します。適切な型が推測できなかった場合には、Stringとみなします。
selectタグで選択された値は、自動的にidに関連付けられたプロパティに格納されます。それでは、selectタグのoptionはどのようにして知ることができるのでしょうか。Teedaは、selectタグのid + "Items"のプロパティがあれば、その情報から、optionの情報を組み立てます。optionに設定するvalue属性とボディ(HTMLの例だとAccountingだとか)はどのようにして組み立てるのでしょうか。Teedaは*Itemsで指定されているCollectionから要素を取り出し、その要素にid属性があれば、それをvalue属性に設定します。ボディの値は、@SelectOneMenuのlabel要素で指定されたプロパティを使います。@SelectOneMenu(value="id",label="name")のように明示的に指定することもできます。nameと指定するとDepartment.nameがリストに表示されます。
churaは、*Itemsの要素がDepartmentであることを知っているので、DepartmentLogicへの参照も自動的にプロパティに追加します。また、リストに表示するので、Departmentを全件取得するんだろうと予想し、initialize()でdepartmentLogic.findAll()を呼び出します。churaは、findAll()が全件取得のメソッドであることも知っているのです。
Teedaは、initialize()というメソッドがあれば、ページを表示する直前に自動的に呼び出します。
自動生成されたソースコードを最初そのまま実行してみましょう。HOT deployに対応してますから、ソースコードを生成した後に、アプリケーションサーバを再起動する必要はありません。部署のリストには、AccountingとResearchと表示されています。Departmentのデータを取得しているはずですが、HTMLに記述した内容がそのまま表示されています。
次に@SelectOneMenuのコメントをはずして、@SelectOneMenu(label="name")に書き換えてemployeeSearch.htmlを再実行してみましょう。Departmentの内容が全件表示されました。@SelectOneMenuの指定が有効になったのです。
ボタンをクリックしてみましょう。まだ、何も記述していないので、何も起きません。
おっと時間だ続きはまた今度。doFindEmployee()の記述からです。

Railsの比べて。

ぼくはひがさんは尊敬しているし、何を考えてこういうことをするかもわかる気がするけど、でも決定的にずれてる。RailsRailsたりうるのはRubyだからで、ほかのものがRailsとくらべてどうこう、ということにほとんど意味はない。

Railsの仕様はほとんど持ち込みませんよ。RubyJavaは違うし。Railsを生産性で越えるということです。今回書いている仕様を見てもRailsと全然違うことが分かるはず。ただ、Railsの良い点は学びたいと思います。