An Easier Java ORM(2) 教訓をJavaに活かす

http://www.javalobby.org/articles/activeobjects/の日本語訳です。<< An Easier Java ORM(1) - 目次に戻る - An Easier Java ORM(3) 実装の詳細を詰める >>

教訓をJavaに活かす - Lessons Learned, Applied to Java

確かにActiveRecordは面白いのだけど、どうしたら役立てられるのでしょう?一つとして、以下の原則を導くことができます。

So, ActiveRecord is interesting, how does this help us? Well, for one thing, we can infer the following axioms:

  • 規約を前提とし、規約をもとに推論する
  • 簡単、簡単、簡単、簡単
  • 少ないコードほどよい
  • APIが多少不規則なドメイン固有言語のようなものであっても、意味があり内部的に整合性を保っていれば問題ない
  • Conventions can be assumed, and conclusions can be drawn from these conventions
  • Simplicity, simplicity, simplicity, simplicity
  • Less code is more
  • It's ok if the API is mildly atypical and DSL-like, as long as it makes sense and remains internally consistent

つまり、理論的には、これらの原則に基づいてJavaであたらしいORMを設計することもできるのです。method_missingのようなRubyの素晴らしい機能を生かすことはできませんが、Javaは、Rubyほど強力ではないにしても、コードを減らすため面白い仕組みをいくつか持っています。最初に焦点を当てるのは、「少ないコードほどよい」です。

So, in theory we could design a brand new ORM, based on these principles, entirely in Java. We may not be able to take advantage of cool Ruby features like method_missing, but Java has its own mechanisms for code reductions which, while not as powerful as Ruby's, still allow for some interesting stuff. The first bit we can focus on is the "Less code is more".

現在ほぼすべてのJavaのORMはPOJOに基づいて利用できます。これは、フィールドやアクセッサ、ミューテータを使ってJava beanを書き、これらのbeanのインスタンスを使い回します。ORMは繰り返しbeanを調べ、値を展開し、(通常XMLで定義される)マッピングロジックを使用し、これらの値のDBへの永続化(もしくはDBからの取得)を行います。これには非常に多くの決まり文句を書く必要があるのです。単純なcompanyテーブルのbeanを定義するために以下ようなものが必要になるでしょう。

Almost every Java ORM currently available is POJO based. This means that you write Java beans, with fields, accessors and mutators, then pass around instances of these beans. The ORM then reflectively interrogates these beans, extracting values and using mapping logic (usually defined in XML) to persist (or retrieve) these values in the database. This requires a lot of excess boiler-plate code. Think about it, to define a simple bean for our companies table would require something like this:

public class Company {  
    private int id;  
    private String name;  
    private String tickerSymbol;  
      
    public Company() {}  
      
    public void getId() {  
        return id;  
    }  
      
    public void setId(int id) {  
        this.id = id;  
    }  
      
    public void getName() {  
        return name;  
    }  
      
    public void setName(String name) {  
        this.name = name;  
    }  
      
    public void getTickerSymbol() {  
        return tickerSymbol;  
    }  
      
    public void setTickerSymbol(String tickerSymbol) {  
        this.tickerSymbol = tickerSymbol;  
    }  
}

これは素晴らしく読みやすいけども、長いです。ActiveRecordの3行のCompanyクラスと比べて、こちらはまだ関連すら扱えていないのです。

While this is nice and readable, it's rather long. Compare this to the three lines required for the ActiveRecord Company class, and we haven't even dealt with relations yet!

最小限の水準でJavaコンパイラの要求を満たすことが可能です。そこで、理論上可能な最少限コードで、以下のようなものが作れます。

At some minimal level, the Java compiler still needs to be satisfied. So even in a theoretically least-possible-code situation, we still need to provide something like the following:

public class Company {  
    public int getID();  
    public void setID(int id);  
      
    public String getName();  
    public void setName(String name);  
      
    public String getTickerSymbol();  
    public void setTickerSymbol(String tickerSymbol);  
} 

同じメソッドがありますが、実装を全く含んでいません。実は、これはインタフェース定義の記述とほとんど同じです。実際に、上記のコードの「class」を「interface」に変えると、コンパイルできるようになります。実際にはまだ何もできませんが、後でできるようにします。重要なのは、companyテーブルをマッピングするのに必要な最小限で最もわかりやすいコードが作れたことです。

All the same methods are still there, but we haven't actually implemented anything. In fact, looking closely at this, we seem to have written an interface definition. In fact, by changing "class" to "interface" in the above code sample, we can make it compile. Of course, it doesn't do anything yet, but we can handle that later. The important part is we've established the minimum and most intuitive code required to map the companies table.

Javaには「動的プロキシ」というあまり知られていないリフレクションの仕組みがあり、これにより開発者がすべてのメソッドを明示的に書かなくても、インタフェースを動的に実装できるようになります。開発者が作る必要があるのはInvocationHandlerの実装だけです。実装は単一のメソッドを持ち、インタフェースの動的なインスタンスに対してのすべてのメソッド呼び出しを処理します。Javaの機能の中でRubyのmethod_missingに最も近いようです。ありがたいことに、これで目的が十分満たされます。

Java has a little-known reflective backwater called "dynamic proxies". Basically, this mechanism allows developers to implement interfaces dynamically, without having to actually write every method explicitly. All the developer has to do is provide an implementation of InvocationHandler. This implementation has a single method, invoke, which handles all method invocations called against the dynamic interface instance. It's about the closest thing Java has to Ruby's method_missing. Fortunately, it's good enough for our purposes.

上のサンプルのインタフェースへのメソッド呼び出すを処理するInvocationHandlerを定義すれば、おそらくフレームワーク内でエンティティの状態をすべて管理し、通常の開発者が決まり文句を書く必要性がなくなります。getID()やsetID(int)メソッドを定義する上位インタフェースを書くこともできます。この考えをさらに拡張し、save()メソッドを定義すると、APIのユーザは値の永続化のために、永続化マネージャに依存するのではなく、エンティティインスタンス自身のメソッドを呼び出せるようになります。

If we define an InvocationHandler which handles the method invocations for our sample interface above, we could conceivably manage the entire entity state within our framework, eliminating the need for the end-developer to write this boilerplate themselves. We could even write a super-interface which defines the getID() and setID(int) methods. Extending this idea even further, we could also define a save() method, to allow API-users to call a method on the entity instance itself to persist values, rather than relying on a persistence manager.

<< An Easier Java ORM(1) - 目次に戻る - An Easier Java ORM(3) 実装の詳細を詰める >>