HoneyAnt

少し時間があったので, HoneyAnt という Eclipse Plugin を公開しました。 これは Eclipse 上でインクリメンタルに Ant を実行するというプラグインで, 特定のアノテーションが付いたファイルを編集すると, 自動的に Ant が実行されるという機能を提供します。

HoneyAnt の使い方

HoneyAnt を clone すると honeyant-feature-updatesite というプロジェクトがあるので, これを Eclipse でインストールしてください。再起動すると, Java プロジェクトのプロパティに「HoneyAnt」という項が増えているはずです。サンプルとして honeyant-example というプロジェクトも用意しているので, ひとまずこれをベースに説明します。

honeyant-example のプロパティを見ると, HoneyAnt が有効になっており, [ Builders ] に HoneyAnt Runner というビルダが追加されていることが分かると思います。これがインクリメンタルビルドを実行するビルダです。次に Person.java というファイルを開いてください。ただのバリューオブジェクトですが, @HoneyAnt というアノテーションを付与しています(※アノテーションは自由に指定出来ます)。ではここに String sex というプロパティを追加して保存してみましょう。

1
2
3
4
5
6
7
8
9
10
11
Buildfile: xxx\honeyant-example\honeyant.xml

honeyant:
     [echo] HoneyAnt incremental build: com.usopla.honeyant.example.source.Person (xxx\honeyant-example\src\main\java\com\usopla\honeyant\example\source\Person.java)
     [dump] dump to xxx\honeyant-example\dest\dump.txt

refresh:
BUILD SUCCESSFUL

BUILD SUCCESSFUL
Total time: 0 seconds

すると自動的に Ant が実行され, このようなログが表示されます。dump.txt を見てみましょう。

1
2
3
4
5
6
7
path=xxx\honeyant-example\src\main\java\com\usopla\honeyant\example\source\Person.java
class : com.usopla.honeyant.example.source.Person
package : com.usopla.honeyant.example.source
fields ->
  name (java.lang.String)
  age (int)
  sex (java.lang.String)

このように編集後のクラス定義を元にファイルが自動生成されています。ここで実行しているのは DumpTask というサンプルの Ant タスクで, クラス情報をリフレクションして適当に出力しています。

何故つくったか?

2 年程前に仕事で新しいパッケージプロダクトを作ることになり, そのときにつくったものです(もちろん同じものではなく, 全体的にリファインして HoneyAnt として公開しています)。当時 Hibernate を使っていたのですが, 折角 O/R マッパーを使っているのにタイプセーフにクエリが書けないことに不満を感じていました。Hibernate には Criteria API というものが存在し, これを使うとある程度オブジェクト指向的にクエリを書くことが出来るのですが, これは(属性名の文字列指定や型情報の欠落を伴う)ランタイムバインドを行うもので, 安全性・保守性・生産性の面から好ましくありませんでした。

1
2
3
4
5
6
7
8
9
10
// Hibernate の Criteria API
// プロパティ名を文字列で指定。引数も Object 型。
Criteria criteria = session.createCriteria(Cat.class);
criteria.add(Restrictions.like("name", "Fritz%"));
criteria.add(
  Restrictions.or(
    Restrictions.eq("age", 0), // 文字列でもバインド出来てしまう
    Restrictions.isNull("age") // プロパティ名を間違えたらランタイムエラーになる
  )
);

「どうにかタイプセーフに出来ないものか」と悩んでいた当時, Slim3 のタイプセーフなクエリに感銘を受けて「コレだ!」と思ったのでした。リフレクションを使うのではなく, 予めエンティティのメタ情報を定義しておき, これを用いてクライテリアを表現すればパフォーマンス面でも型安全性の面でも良いのだと気付いたのでした。また 「折角メタ情報を作るのであれば, エンティティのメタ定義からエンティティ, DDL, hbm, クライテリア用のメタクラスも全て自動生成すれば夢が広がるなぁ」などと思いつき, これを実現する方法を考えてみることにしました。

自動生成の実現にあたって, 最初は Slim3 と同じように APT (Annotation Processing Tool) を用いた自動生成を行おうとしたのですが, APT は意外と自由度が低くファイルの自動生成にはあまり向いていないなと感じました。当初エンティティのメタ情報を Java もしくは Groovy クラスとして定義し, そのクラス情報をリフレクション経由で取得してファイルを自動生成しようとしていたのですが, APT を使うと修正しているファイルのクラス情報を完全に取得することが出来ません(でした……今は違うのかもしれません)。「特定のファイルを編集したときに, もっと自由に自動生成したいなぁ」と思い, Eclipse の Ant を自動的に実行すれば良いかな、と思いついたのでひとまず Eclipse の Plugin をプロトタイプしてみることにしました。Plugin の作り方なんてさっぱり知らなかったのですが, 本やコードを読みながら 2〜3 日ほどかけて実装してみると, 思いのほか上手くいったので APT は止めて Eclipse Plugin にすることにしました。結果的にできあがったのが HoneyAnt でした。

実際にどのように使ったか?

さて, 当時の PJ ではエンティティは全てメタ定義(Java クラス・ランタイムでアプリケーションに含まれない)を元に HoneyAnt で自動生成しました。例えば Redmine のようなプロジェクト管理アプリケーションの「プロジェクト」をサンプルにすると, メタ定義はこんな感じです(適当な例にしています)。テーブル名やカラム名をデフォルトで推測したり, Hibernate の主要な機能は一通り使えるようにしたりしました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@HoneyAnt("ユーザー")
public class UserSource extends ClassSource {

    @Property("ID")
    NumberProperty<Long> id = sequenceIdProperty();

    @Property("名前")
    StringProperty name = stringProperty(20).notNull();

    @Property("メールアドレス")
    StringProperty email = stringProperty(50).notNull();

    @Property("管理者")
    BooleanProperty admin = primitiveBooleanProperty();

    @Property("ステータス")
    UserTypeProperty<UserStatus> status = userTypeProperty(UserStatus.class).notNull();

}


@HoneyAnt("プロジェクト")
public class ProjectSource extends ClassSource {

    @Property("ID")
    NumberProperty<Long> id = sequenceIdProperty();

    @Property("序数")
    NumberProperty<Integer> sequenceNo = primitiveIntProperty(10);

    @Property("名称")
    StringProperty name = stringProperty(50).notNull();

    @Property("ステータス")
    UserTypeProperty<ProjectStatus> status = userTypeProperty(ProjectStatus.class).notNull();

    @Property("メンバー")
    @Index(name = "IXA_PROJECT_01")
    ManyToManyProperty<User>> users = manyToMany(UserSource.class, "PROJECT_USER_RELATION").lazy(true);

}

このファイルからおおよそ以下が HoneyAnt により自動生成されます。

  1. エンティティ・エンティティの基底クラス(Generation Gap パターン)
  2. エンティティメタクラス・エンティティメタ基底クラス(Generation Gap パターン)
  3. hbm
  4. DDL
  5. テーブル定義書等

これだけでも十分便利なのですが, このときに一番実現したかったものは「タイプセーフなクエリ」です。これを実現する際の肝になっているのが 2. の「エンティティメタクラス」です。このメタクラスを使用してタイプセーフクライテリアを実現しました。タイプセーフクライテリアの実現に当たっては, まずタイプセーフなクライテリア API を作り, 次に HoneyAnt でこの API を利用したエンティティメタクラスを自動生成するようにしました。エンティティメタクラスを利用したタイプセーフなクライテリアは, 以下の場面で使用しました。

  • DAO(Hibernate の Criteria API に変換してタイプセーフなクエリを実現)
  • 継続クエリ(OQL に変換してサーバサイドに登録し, メッセージフィルタとして使用)

クライテリア API を独立させることによって, 様々な場面で活用出来る拡張性を持たせています。パーサさえ書けば様々な要素にバインド出来るので, MongoDB 用のアダプタなんかも作ろうかなと思っていました(このときは使用しなかったので作りませんでしたが)。

では実際に使う場合の簡単な例を紹介します。DAO として使用する場合はこんな感じです(クエリの意味は適当ですスミマセン)。ここではひとつずつ検索条件をその場で作っていますが、Rails の Named Scope のようにメタクラスに条件を定義することも出来ます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
ProjectMeta meta = ProjectMeta.getInstance();
Dao<Project> dao = daoFactory.createDao(meta);

// ex. update
dao.save(project);
dao.update(project);
dao.delete(project);

// ex. entity-query(エンティティ単位)
{
    EntityQuery<Project> query = dao.createQuery();
    query.orderBy(meta.id, Order.ASC);
    Criteria<Project> criteria = meta.createCriteria().add(meta.sequenceNo.lt(5)).add(meta.status.eq(ProjectStatus.ACTIVE));
    List<Project> projects = query.list(criteria);
}

// ex. projection-query(射影)
{
    ProjectionQuery<Project> query = dao.createProjectionQuery();
    Criteria<Project> criteria = meta.createCriteria().add(meta.users.admin.isTrue()).add(meta.users.status.eq(UserStatus.ACTIVE));
    List<Long> userIds = query.listValues(criteria, meta.id); // もちろん複数プロパティの射影も可能
}

// ex. aggregation-query(集約)
{
    AggregationQuery<Project> query = dao.createAggregationQuery();
    AggregationFunctionProperty<Project, Long> maxUserIdProperty = Aggregations.max(meta.users.id);
    Criteria<Project> criteria = meta.createCriteria().add(meta.id.eq(1L)).add(meta.status.eq(UserStatus.ACTIVE));
    Long maxUserId = query.uniqueValue(criteria, maxUserIdProperty);
}

見ての通り一通りタイプセーフにクエリが出来るようになっています。当然ですがクライテリアには強い型制約を持たせています。上記の例ではプロパティ同士の比較はしていませんが, 例えばプロパティ同士の比較クライテリオン ge (greater than or equals to) はこんな感じのシグネチャです。

1
2
// E はエンティティの型・C は比較される値の型
<E, C extends Comparable<? super C>> PropertyCompareCriterion<E, C, ComparableProperty<? super E, C>> ge(ComparableProperty<? super E, C> condition)

かなり強い型制約を持たせていることが分かるかと思います。

継続クエリとして使用するときも同じクライテリア API を用いることが出来ます。

1
2
3
4
ProjectMeta meta = ProjectMeta.getInstance();
Criteria<Project> criteria = meta.createCriteria().add(meta.status.eq(ProjectStatus.ACTIVE));
CriteriaFilter filter = CriteriaFilterFactory.create(criteria);
boolean b = filter.apply(project);

同じ API を使用しているので, 検索条件をひとつ作れば DB アクセスにも継続フィルタを使用した非同期更新キャッシュなんかにも使うことが出来ます。

というわけで

色々な用途に使えると思うので, 良ければご自由にお使い下さい。

Java で DSL を作るのは難しいです。DSL を定義するのであれば Groovy や Ruby を使った方がよほど楽でしょう。ですので Java で DSL を無理矢理つくるよりは、タイプセーフなメタ情報を自動生成するのが個人的にはオススメです。今なら Xtext を使うのもひとつ有効な手段なのかもしれませんが、使い慣れた Java で Ant タスクとして自動生成処理を定義したい場合には HoneyAnt を使うのも良いかなと思います。

git

Subversion to Git

勤務先は未だに Subversion を使っているのですが、いい加減 git を使いましょうということで先日勉強会を開催したのでそのときに使用した資料を Speaker Deck にアップロードしてみました。一部社外に公開出来ないところは削除したり改変したりしているため少々おかしいところがありますが、殆ど社内勉強会で使用した資料と同じものです。良ければご参照ください。勤務先では基本的に Windows + Eclipse 環境で開発している人が多いので、Eclipse や GitHub for Windows によるデモを交えた勉強会にしました。そのため、資料中でも EGitGitHub for Windows に関する記述が出てきます。ちなみに EGit は今回初めて評価してみたのですが、思った以上に良く出来ていて業務での利用には全く問題ないと判断しました。勿論 git の様々な操作を全て使用出来るようにはなっていませんが、一般的なコマンドについてはカバーしていますし、難しい操作については GitHub for Windows に付属する Shell を使えば問題ありません。

Subversion の嫌なところ

Subversion を使っていて日常的に困るのは以下のようなケースです。

  1. 「とりあえずコミット」が横行するのできちんとしたコードレビューが難しい(レビュー対象が散乱する)
  2. 手元の作業記録を簡単に記録出来ないので気楽にリファクタリングすることが出来ない(大規模なリファクタリング中にあれこれ試すのが難しい)
  3. (同じ理由により)並行作業がしづらい(手元でコードを書いているときに別作業を頼まれたら苦労する)

git にはローカルリポジトリがあるのでこのような問題点が解決出来ます。

  1. コミットを整理してから push 出来る
  2. 手元で色々なブランチを作って作業出来る
  3. 作業途中で stash 出来る

素敵ですよね。

git が生み出すもの

勉強会では git を採用することのメリットについてあれこれ話しました。「git を導入したらどれぐらい生産性が上がるの?」というのは、PC やモニタを買うのと一緒でなかなか数値化することが難しいものではありますが、少なくとも参加した皆さんにはある程度良い感触を持ってもらえた思えてもらえたんじゃないかな……?と思います。 個人的には

  1. バリバリ開発している人(コア開発者)ほど生産性が上がる
  2. (コア開発者は大抵コードレビューも行うので)コードレビューの時間が取れるようになる
  3. コードレビューの時間が取れる + git 効果でレビューしやすくなるのでレビューの質が上がる
  4. 細かなコードレビューでジュニアなメンバーのスキルアップにもつながる
  5. ますますコア開発者の生産性が上がる

という良いサイクルを生み出すことが出来るんじゃないかと思っています。また、GitHub や Stash を導入することでエンジニア同士の交流が活性化して、会社の雰囲気や文化が少しでも変わっていく端緒となれば良いなと期待しています。

気付いたこと

今まで勉強会など参加したことも殆ど無かったですし、ましてや自分が主催したり発表したりすることも勿論無かったので、今回が初めての経験でした。いざ自分でやってみると、勉強会に参加する態度によって学習効果が大きく違うんだなということを再確認しました。

  • 受動的に参加する(相手が言っていることを鵜呑みにするだけ)
  • 能動的に参加する(相手が言っていることを理解しようと努める・疑問を持つ・質問する)
  • 自分でプレゼンする

下にいくほど学習効果が高いわけです。良く言われていることですが、今回改めてそのことを実感しました。勉強会でプレゼンしようと思うと事前にしっかり準備しないといけません。資料をつくったり、理解が曖昧なところを深堀しないといけません。

何かを学ぶとき、(相当頭の良い人は別だけど)ふつうの人は受動的に情報を仕入れているだけでは想像力が働きません。けれど人に説明するためにしっかり理解しようとすると、その過程で様々な疑問が出てくる。「調べてみるとこんなことが言われているけど、いったいどういうことなんだろう?」「これはもしかしてこういうことなんじゃないか?」などと自分の想像力を働かせる機会が増え、曖昧だった知識が体系化され、自分の中の血肉として育っていく。だから勉強会をするのは(例え自分が理解している「つもり」の物事をテーマにしたとしても)意味のある行為だな、今後はもっと自分で勉強会を主催しよう、などと思ったのでした。

Hello Octopress

前々から日記以外に技術系のブログをひとつ作りたかったので Octopress + GitHub Pages で構築してみました。人に見られることを想定したブログというものはここ数年作っていなかったので本当に久しぶり。主に技術ネタを扱っていきたいと思っているので横幅を広く取れるテーマにしてみました。以下備忘録。

ブログの設定を変更したとき

1
2
3
$ git add .
$ git commit -m 'message'
$ git push origin source

記事を書くとき

1
2
3
4
5
$ bundle exec rake new_post["title_in_english"]
$ vim source/_posts/your_new_post.markdown
$ bundle exec rake generate
$ bundle exec rake preview -- http://localhost:4000
$ bundle exec rake gen_deploy