本文へジャンプ

クラシックJ2EEアーキテクチャーからの脱却

Lightweightコンテナ・アーキテクチャーへの招待

 
レベル: 上級者向け
1/2 2/2

2005年09月07日(水)
日本アイ・ビー・エム株式会社 ソフトウェア事業
夷藤 勇人
Lightweightコンテナを使用したJ2EE開発は、その柔軟性・開発生産性を武器に、今日のJ2EEを席巻しつつあります。その根底を支えるのは、DI(Dependency Injection: 依存性注入)+AOP(Aspect Oriented Programming:アスペクト指向プログラミング)という強力なパラダイムです。

この記事は、従来の"重い"J2EE開発に対して疑問を感じている方、その開発生産性に対して日々不満を抱いている方たちが対象です。クラシックJ2EEアーキテクチャーからの脱却を願う開発者にとっての新たなる希望「Lightweightコンテナ・アーキテクチャー」を紹介します。
インデックス
クラシックJ2EEアーキテクチャーとは
EJB is Old Fashioned?
Lightweightコンテナ・アーキテクチャー
POJOベースのO/Rマッピング
プログラミングtoインターフェース
Dependency Injectionとは?
AOP(Aspect Oriented Programming)による宣言的トランザクション
テスト容易性(Testability)の確保
非侵略性(non-invasiveness)
アプリケーションサーバー環境でPOJOにサービス
Java EE 5へ向けて
.NET開発者へ
最後に
この記事は、個々のフレームワーク・ツールの詳細な使用法・技術解説が目的ではありません。
この記事は、ビジネスロジックレイヤーに焦点をあてます。Web・UIレイヤーは対象外とします。
特に注釈がない限り、この記事でEJBという場合は、J2EE 1.4までの、EJB 1.1からEJB 2.1、特にステートレス・セッションビーンのことを指します。
クラシックJ2EEアーキテクチャーとは
クラシックJ2EEアーキテクチャーに基づいたアプリケーションの代表例としては、J2EE開発者にとってはおなじみの“PetStore”があげられます。J2EE Blueprintsで使用されているサンプルアプリケーションで、J2EE開発者にとってお手本となるべく作成されました。ビジネスロジックレイヤーの実装にEJBを多用しているのが主な特徴です。

ここ数年にわたる、J2EEコミュニティーからのアンチEJBムーブメントにより、この元祖“クラシック”Petstoreは、格好の非難の的となってきました。

  • 「J2EEデザインパターンといいつつ実はアンチパターン集」

  • 「必要のないオーバー・エンジニアリング」

なかでもクラシックJ2EEアーキテクチャーでもっとも槍玉にあげられたのは、EJBを全面的に使用している点です。

テスタビリティー(Testability : テスト容易性)は、アジャイル開発に代表される・今日のソフトウェア開発における最重要要素です。これは、J2EE開発プロジェクトにおいても例外ではありません。J2EE開発プロジェクトにおいて、テスタビリティーが阻害されるのは、なんといってもEJBを使用した時です。当たり前のことですが、EJBはEJBコンテナ内でしか動作しません。さらに悪いことに、EJBコンポーネントは取り除くことが困難な多くの外部要因に依存しがちです。

テスト・ドリブンな開発を進める上で、このような「テストのやりづらさ」は致命的な障壁となります。ユニットテストの本来の目的とは関係のない外部要素を全て用意しておかないとテストを実行できません。
上に戻る
EJB is Old Fashioned?
少し歴史を振り返ってみましょう。EJB 1.0の仕様がはじめて制定されたのは、1998年のことです。当時の状況と比べて、今日ではJavaはいくつかの重要な進化をとげています。

  • J2SE 1.3におけるDynamic Proxy(動的プロクシ/java.lang.reflect.Proxy)の導入。これにより、事前の静的なコード生成の必要性が大幅になくなりました。

  • CGLIBに代表される、実行時バイトコード生成テクノロジーの成熟。

  • JVMにおける世代別GC(ガベージ・コレクション)の実装。これにより、小さなオブジェクトの生成・回収に関しては、ほとんどそのコストを意識する必要はなくなりました。

EJBはこれらの恩恵をまったく受けることのできなかった時代に制定されたものです。当時の状況にもとづいた思想・制限をいまだに引きずっている面があります。

  • 事前のデプロイメント・コード生成を前提とした開発モデル
    今では、Dynamic Proxy、あるいは実行時コード生成の使用を前提にした開発モデルになるでしょう。こちらの方がよりスマートで、開発者に余分な負担をかけません。

  • 分散オブジェクトが前提
    EJB 2.0からはローカル・インターフェースが導入されましたが、分散オブジェクトが前提という負のプログラミング・モデルは相変わらず残っています。必要のないところに「分散」をもちこむのは悪以外の何者でもありません。

  • 1インスタンス1スレッドの強制
    たとえば、Servletを考えてみましょう。Servletは標準で1インスタンスを複数スレッドで使用するモデルです。SingleThreadModelはもはや非推奨になりました。しかし、ステートレス・セッションビーンはあいかわらず、1インスタンスは1スレッドのみという強制から逃れるすべはありません。たとえ、スレッドセーフなステートレス・セッションビーンを作成したとしてもです。Servletでは非推奨になったはずのSingleThreadModelのみしか選択肢が開発者には与えられないのです。

  • インスタンスプールモデル
    JVMがオブジェクトの生成・回収が苦手だったころでは納得できる考え方でした。しかし、今日のモダンなJVM上では生成コストが無視できるようなステートレスなインスタンスをプールしておく意義はもはや存在しません。

  • JNDIに代表されるDependency Lookupにもとづく依存オブジェクトの取得
    今日では、Dependency Injection(依存性注入: DIについては後述)を用いて、依存性を解決するのがスマートです。

EJBの複雑な開発モデルは、開発者に無駄な負担をかけています。

もっと楽をしましょう。

いつまでもひとつのモデルに縛られる必要はありません。J2EEコミュニティーから生まれたイノベーションの恩恵を我々は受けてもよいはずです。EJBを使用しなければ不可能だったことが、EJBを使用しなくても・より"Lightweight"な方法で実現できる時代です。
上に戻る
Lightweightコンテナ・アーキテクチャー
Lightweightコンテナ・アーキテクチャーは、J2EEコミュニティーから生まれたイノベーションを武器とした、“軽量”かつ“柔軟”なアプローチです。基本となる設定思想は、古き良きただのJavaのオブジェクト・「POJO(Plain Old Java Object)」への回帰です。

EJBのように、特定のインターフェース・クラスの実装・継承を強制されることは良しとしません。それでは、せっかく作成したコンポーネントが特定の環境に依存してしまいます。

Lightweightコンテナ・アーキテクチャーでは、ビジネスロジックの実装であるビジネス・オブジェクトはPOJOで実装します。

様々なサービス・特定の環境への依存性などは、POJO内にはあらかじめ書いておきません。実行時に動的にPOJOに組み込みます。

EJBがEJBコンテナに管理されるのと同様に、POJOはLightweightコンテナによって管理されます。コンテナというと特別な印象を持ってしまうかもしれませんが、実態はただのPure Javaなライブラリーです。あらゆるJava環境から利用できます。

今回は、最初にJava単体、すなわちJ2SE環境で、Lightweightコンテナを使用したサンプルを動かします。J2EEアプリケーションサーバーは必要としません。

その後、今度は、コードを一切変更しないでそのままJ2EEアプリケーションサーバー環境上で同様に動作させてみます。
上に戻る
POJOベースのO/Rマッピング
まず、Lightweightコンテナ・アーキテクチャーの真髄である、DI + AOPの説明をする前に、ビジネスロジックレイヤーを実装する上で不可欠なデータパーシステンスの実装の話をします。

今回のサンプルでは、ORM(Object Relational Mapping)ツールとして、標準の地位となりつつあるHibernateを使用しています。例として、「Customer」エンティティーの定義を示します。

POJOベースのエンティティー定義:

import javax.persistence.*;
...

@Entity
public class Customer {
  private Integer id;
  private String name;
  Set<Order> orders = new HashSet<Order>();
  
  @Id(generate=GeneratorType.AUTO)    
  public Integer getId() {
      return id;
  }
  public void setId(int id) {
      this.id = id;
  }
  public String getName() {
      return name;
  }
  public void setName(String name) {
      this.name = name;
  }

  @OneToMany(mappedBy="customer", cascade=CascadeType.ALL,
             fetch=FetchType.EAGER)
  public Set<Order> getOrders() {
      return orders;
  }
  
  public void setOrders(Set<Order> orders) {
      this.orders = orders;
  }
Hibernateを使用する場合は、別途設定ファイルにO/Rマッピング情報を定義するのが一般的ですが、今回は、アノテーションを使用して直接ソースコード上にマッピング情報を定義します。

使用しているアノテーションは、「JSR220-EJB 3.0 Persistence API」で定義されているアノテーションです。「Persistence API」は、もともとはEJB 3のために規定されていたものですが、EJBコンテナ外でも使用できるようにと、APIが独立しました。パッケージ名も、ずばりjavax.persistence.*です。

これを受けて、HibernateではO/Rマッピングの定義に、Persistence APIで規定されているアノテーションを使用できるようになりました。
上に戻る
プログラミング to インターフェース
インターフェースに対してプログラミングするというのは、柔軟なアーキテクチャーにとっての基本です。今回の、ビジネスロジックの構成を図1に示します。

図1 ビジネスロジックレイヤー
クライアントに公開するインターフェースとして、AppServiceを用意しています。AppSerivceImplがその実装です。CustomerDao, OrderDaoといったDAO(Data Access Object)を使用してビジネスロジックを実装しています。各DAOの実装クラスは、HibernateのSessionFactoryを使用しています。
上に戻る
Dependency Injectionとは?
インターフェースに対してプログラミングするのがよいというのは、誰もが理解しているところです。ところが、実際にはインターフェースの積極的使用に関してはちょっとした心理的抵抗があります。「そのインターフェースの実装クラスをどうやって取得する?」という根本的な問題があるからです。

ビジネスロジックの実装クラスであるAppServiceImplを例にとってみましょう。このクラスは、インターフェースOrderDaoとCustomerDaoを使用して、ビジネスロジックを実装しています。

AppServiceImplの立場になって考えてみましょう。各DAOの実装クラスを、どうやって入手すればよいでしょうか?

インターフェースの実装クラスを直接指定する:

public class AppServiceImpl implements AppService {
  CustomerDao customerDao = new CustomerDaoImpl();
  ....
このように直接コードに書いてしまっては、CustomerDaoの具象クラス、CustomerDaoImplに依存してしまいます。

それでは、ファクトリーを用いて実装クラスを隠蔽するようにしてみてはどうでしょうか?

ファクトリー経由でインターフェースの実装クラスを取得する:

public class AppServiceImpl implements AppService {
  CustomerDao customerDao = AdHocFactory.getCustomerDao();
  ....
確かに、実装クラスは隠蔽することができました。その代わり、今度はファクトリーへの依存性がひとつ追加されてしまいました。AppServiceImplは単にCustomerDaoを使用したいだけなのです。ファクトリーの使用方法など知りたくはありません。本来の責務外です。

そこで、登場するのがDependency Injection - DIです。DIの考え方は、こうです。

オブジェクト自身は、依存オブジェクトを宣言するだけです。自分からは探しにいきません。個々のオブジェクトがそのようなことを気にする必要はないのです。

その代わり、外部から「依存オブジェクト(Dependency)を注入(Injection)」してもらうのです。どのように依存性を注入するかについてはいろいろ方法があります。一例として、以下のようにセッターメソッド経由で、依存オブジェクトを受け取る方法があります。

依存性の宣言 - セッターメソッドの提供:

public class AppServiceImpl implements AppService {
  CustomerDao customerDao;
  ....
  public void setCustomerDao(CustomerDao customerDao) {
      this.customerDao = customerDao;
  }
適切な依存オブジェクトをパラメーターとしてセッターメソッドをコールすることによって依存オブジェクトを注入できます。

このようにセッターメソッドを用意しておけば、以下のように外部から各オブジェクトの依存オブジェクトを注入できます。

依存オブジェクトの注入:

DataSource dataSource = .....;
SessionFactory sessionFactory = .... // dataSourceを使用;

CustomerDaoImpl customerDao = new CustomerDaoImpl();
customerDao.setSessionFactory(sessionFactory);

OrderDaoImpl orderDao = new OrderDaoImpl();
orderDao.setSessionFactory(sessionFactory);

AppServiceImpl appServiceTarget = new AppServiceImpl();
appServiceTarget.setCustomerDao(customerDao);
appServiceTarget.setOrderDao(orderDao);
しかし、依存性の解決をこのようにハードコーディングしていては、あまりエレガントではありません。各オブジェクトの依存関係を、「設定ファイル」で記述できればより高いレベルの柔軟性を手にすることができます。

そこで登場するのが、DIコンテナです。今回は、DIコンテナの代表例として、Springフレームワークを使用します。Springフレームワークを使用した場合の、設定ファイルは以下のようになります。

Springフレームワークによる設定ベースの依存性注入:

<beans>
  <bean id="myDataSource"
        class="org.apache.commons.dbcp.BasicDataSource" 
        destroy-method="close">
      <property name="driverClassName" 
                value="org.hsqldb.jdbcDriver"/>
      ...
  </bean>

  <bean id="mySessionFactory"
        class=".....AnnotationSessionFactoryBean">
      ....
      <property name="dataSource" ref="myDataSource"/>
      ....
  </bean>

  <bean id="myAbstractDao" abstract="true">
      <property name="sessionFactory" ref="mySessionFactory"/>
  </bean>

  <bean id="myCustomerDao" 
        class="com.example.CustomerDaoImpl"
        parent="myAbstractDao"/>

  <bean id="myOrderDao" class="com.example.OrderDaoImpl"
        parent="myAbstractDao"/>

  <bean id="myAppServiceTarget" 
        class="com.example.AppServiceImpl">
      <property name="customerDao" ref="myCustomerDao"/>
      <property name="orderDao" ref="myOrderDao"/>
  </bean>
この設定を受け、以下のように依存オブジェクトが注入されます。

  1. データソースを定義(id: myDataSource)

  2. HibernateのSessionFactoryに1.のデータソースを注入(id: mySessionFactory)

  3. CustomerDaoImplに2.のSessionFactoryを注入(id:myCustomerDao)

  4. OrderDaoImplに2.のSessionFactoryを注入(id:myOrderDao)

  5. AppServiceImplに3.と4.の各DAO実装を注入(id:myAppServiceTarget)

DIが解決しようとしている事柄があまりに根本的なことなので、最初はなかなかDIのメリットが理解できないかもしれません。ユニットテストの重要性が最初はなかなか理解できなかったことを思い出してください。

まるでレゴ・ブロックを組み立てるかのように、依存性の解決を一貫した設定ファイルベースで行えるという事実は、想像以上の快適さ・柔軟性を与えてくれます。
上に戻る
AOP(Aspect Oriented Programming)による宣言的トランザクション
EJBが提供する宣言的トランザクションサービスは、非常に魅力的です。プログラミング・モデルの大幅に簡略化につながるもので、その恩恵の大きさは、EJBの採用を正当化できる理由の一つです。

しかし、宣言的トランザクションは、もはやEJBの専売特許ではありません。DI + AOPの恩恵を受けることによって、POJOベースで実現可能です。今回は、やはり特別な環境を用意することなく、「ただのJ2SE環境」で、宣言的トランザクションを実現します。

AOPの出番です。

Springフレームワーク・AOPによる宣言的トランザクションの織り込み:

<beans>

  <bean id="myTxManager" 
        class="org.springframework.orm.hibernate3.
               HibernateTransactionManager">
      <property name="sessionFactory" ref="mySessionFactory"/>
  </bean>

  <bean id="myAppService" 
        class="org.springframework.transaction.interceptor.
               TransactionProxyFactoryBean">
      <property name="transactionManager" ref="myTxManager"/>
      <property name="target" ref="myAppServiceTarget"/>

      <property name="transactionAttributes">
          <props>
              <prop key="*">PROPAGATION_REQUIRED</prop>
          </props>
      </property>

  </bean>
targetで指定されているid - myAppServiceTargetとは、実際のビジネスロジック実装であるAppServiceImplを指しています。このPOJOに対して宣言的トランザクションサービスというアスペクトを織り込んでいます。この例では、すべてのメソッドのトランザクション属性を"Required"に設定しています。

これで、全ての準備が整いました。ビジネスロジックのクライアントは、DIコンテナからPOJOを取り出して使用することになります。

DIコンテナからオブジェクトの取得:

ApplicationContext context = ...
AppService appService = (AppService) context.getBean("myAppService");
// call business logic
....
上に戻る
2/2へ
レベルマークについて

このページで紹介されている情報はレベル別にカテゴライズされています。

上級者向け
中級者向け
初級者向け
入門者向け