EJBコンテナが分散コンポーネントモデルから軽量なDIコンテナに変化してきた歴史を振り返る

十年一昔といいますが、文字通り一昔前の書籍ではJ2EEEJBコンポーネントはプロセスが分散化されたリモート呼び出しにより処理を行う分散コンポーネントとして説明されています。そして、残念ながら現状Java EE関連の日本語の書籍はこうした古い時代に書かれたものがほとんどとなっています。それゆえ、

  • 開発効率がきわめて悪い
  • 実行性能が悪い*1
  • 仕様がきわめて複雑で理解が大変

といった悪いイメージが定着してしまっているのではないかと思います。しかしながら、最新バージョンのJava EE6では、Spring、GuiceSeamなどのOSSの軽量コンテナのアイデアを取り込むことにより、以前とは比較にならないくらい開発効率が改善されているという事実があります。
ここでは、Hello WorldEJBの書き方を以前の古いバージョンから順次振り返りながら比較してみることで、EJBのプログラミングモデルがどのように変化してきたのかについて考えてみることにしたいと思います。古い仕様のEJBを使ったシステムをメンテナンスする必要のない幸運な人も、以前と比較して現在どのくらい便利になったのかを知ることは興味深いことだと思います。

EJB1.xの時代は純粋に分散コンポーネントの仕様だった(1998年〜2001年)

今では全く信じられないことですが、もともとEJBの仕様が登場してきた当時はEJBはCORBAやWebサービスと同じように別プロセスから呼び出される分散コンポーネントを作成するための仕様としてスタートしました。分散コンポーネントですから、POJOのような普通のクラスと違ってプログラマーが気軽に作成するようなものとは当然考えられておらず、一つのサブシステムのようにある程度複雑な責務をカプセル化した粒度の大きな単位のコンポーネントとして作成するものと想定されていたのです。それゆえ、

  • 事前にインターフェースを明確に定義しておく
  • ソースコードに手を加えずにxmlファイルで独立して設定を変えられるように柔軟性を持たせておく
  • ある程度呼び出しや作成が面倒でも目をつぶる
  • インスタンスプールなどサーバーのリソースを多く消費する

といった思想で仕様が設計されたのだと思います。この頃からコンポーネントの目的に応じてステーレスセッションBean、ステートフルセッションBean、エンティティBeanといった種類がありましたが、ここではEJBとしてもっとも単純なステートレスセッションBeanを定義することを考えます。
まず、EJB1.1では以下の2種類のインターフェースを定義する必要があります。

  • リモートホームインターフェース(EJBを生成するためのファクトリとして使うためのインターフェースを定義)
  • リモートコンポーネントインターフェース(クライアントから呼び出し可能な業務処理のインターフェースを定義)

まず、ホームインターフェースは規約に従って以下のように定義します。これは決まりきった形なのですが、EJBHomeを継承し、create()メソッドの戻り値型はリモートインターフェースとなっており、チェック例外であるCreateExceptionとRemoteExceptionを必ずthrowsすると宣言する必要があることに注意してください。

package hello.ejb;

import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;

public interface HelloHome extends EJBHome {
    Hello create() throws CreateException, RemoteException;
}

一方、リモートインタフェースの方は以下のようになります。EJBObjectを継承し、ビジネスメソッドに対応するメソッドを定義しますが、ここでもRemoteExceptionの送出を宣言することが必須です。また、パラメーターや戻り値はRMI互換となるため、シリアライズ可能型など制約があります。

package hello.ejb;

import java.rmi.RemoteException;
import javax.ejb.EJBObject;

public interface Hello extends EJBObject { 
    String sayHello(String name) throws RemoteException;
}

次に、EJBの実装クラスは以下のように作成します。

package hello.ejb;

import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class HelloBean implements SessionBean { // 間違えやすいのですが、Hello自体はimplementsしない。
    
    private SessionContext context;

    private String message;
    
    public void setSessionContext(SessionContext aContext) {
        context = aContext;
    }

    public void ejbActivate() {}

    public void ejbPassivate() {}

    public void ejbRemove() {}

    // EJBのインスタンスが生成されるときに一度だけ呼び出される初期化処理
    public void ejbCreate() {
        // ejb-jar.xmlで定義された環境エントリからメッセージ文字列を取得する。
        Context ic = null;
        try {
            ic = new InitialContext();
            message = (String)ic.lookup("java:comp/env/message");

        } catch (NamingException ex) {
            throw new RuntimeException(ex);
        } finally {
            if (ic != null) {
                try {
                    ic.close();
                } catch (NamingException ex) {
                    throw new RuntimeException(ex);
                }
            }
        }
    }
 

   // リモートコンポーネントインターフェースで宣言したビジネスメソッドを定義する

    public String sayHello(String name) {
        return message + " " + name;
    }

}

以上のEJBの実装クラスでポイントは以下の通りです。

  • ビジネスメソッドはリモートコンポーネントインターフェースのメソッドに対応しているが、直接Helloインターフェースを実装してはならない。
  • SessionBeanインターフェースを実装しなくてはならない。(このためejbXX()メソッドなど多くのコールバックメソッドを空でもよいので実装する必要がある。)
  • コンストラクタではなくてejbEreate()で初期化処理を行う。(ここでは例としてメッセージ文字列を標準のxmlファイルからルックアップしてEJBのフィールドに格納する例を示しています。)

そして、さらに以上の3つのクラスやインターフェースに対応してejb-jar.xmlというデプロイメント記述子を以下のように作成する必要があります。

<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar version="2.1" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd">
    <display-name>EJB2App-ejb</display-name>
    <enterprise-beans>
        <session>
            <display-name>HelloBeanSB</display-name>
            <ejb-name>HelloBean</ejb-name>
            <home>hello.ejb.HelloHome</home>
            <remote>hello.ejb.Hello</remote>
            <ejb-class>hello.ejb.HelloBean</ejb-class>
            <session-type>Stateless</session-type>
            <transaction-type>Container</transaction-type>
            <env-entry>
                <env-entry-name>message</env-entry-name>
                <env-entry-type>java.lang.String</env-entry-type>
                <env-entry-value>こんにちは</env-entry-value>
            </env-entry>
        </session>
    </enterprise-beans>
    <assembly-descriptor>
        <container-transaction>
            <method>
                <ejb-name>HelloBean</ejb-name>
                <method-name>*</method-name>
            </method>
            <trans-attribute>Required</trans-attribute>
        </container-transaction>
    </assembly-descriptor>
</ejb-jar>

以上で、ようやくHello EJBの作成と定義が終わりデプロイできる状態となりました。次に、このEJBをWeb層のサーブレットから呼び出すには以下の手順に従う必要があります。

  • HelloHomeをJNDIからルックアップ
  • HelloHomeに対してcreate()メソッドを呼び出すことでHelloインタフェースのEJBオブジェクト(リモートProxy)を生成
  • 生成したHelloに対してビジネスメソッドを呼び出す

まず、HomeインターフェースのルックアップはJNDIのAPIを利用して行いますが、これだけで相当面倒なコーディングが必要なため、ベストプラクティスとして
J2EEパターンではService Locatorパターンを使うことが推奨されています。例えば、以下のようなルックアップ専用のクラスを作成します。

package hello.web;

import hello.ejb.*;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;

public class HelloServiceLocator {

    public static HelloHome lookupHelloHome() {
        Context context = null;
        try {
            context = new InitialContext();
            // 実際にはHomeの参照をキャッシュした方がベター
            return (HelloHome) PortableRemoteObject.narrow(
                    context.lookup("java:global/EJB2App/EJB2App-ejb/HelloBean!hello.ejb.HelloHome"), HelloHome.class);
        } catch (NamingException ex) {
            throw new RuntimeException(ex);
        } finally {
            if (context != null) {
                try {
                    context.close();
                } catch (NamingException ex) {
                    throw new RuntimeException(ex);
                }
            }
        }
    }

なお、実は以上の実装には可搬性の上でも性能の上でも問題があります。なぜなら、アプリケーションサーバーに固有のJNDI名を使って直接ルックアップしているからです。JavaEE6でこうしたJNDI名の標準化が行われるまではJNDI名の文字列としてサーバーごとにそれぞれ別々の形式を使わなくてはならなかったのです。そこで、ちょっと面倒なのですが、当時として正しくEJBを参照するにはweb.xmlにて以下のようにリソース参照を定義するのが推奨される方法です。

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
...
    <ejb-ref>
        <ejb-ref-name>ejb/hello</ejb-ref-name>
        <ejb-ref-type>Session</ejb-ref-type>
        <home>hello.ejb.HelloHome</home>
        <remote>hello.ejb.Hello</remote>
        <ejb-link>HelloBean</ejb-link>
    </ejb-ref>
</web-app>

そうすると、ServiceLocatorでルックアップしている部分は、以下のように記述できます。

    return (HelloHome) PortableRemoteObject.narrow(
        context.lookup("java:comp/env/ejb/hello"), HelloHome.class);

WebアプリケーションやEJBなどJ2EEコンポーネントごとに環境エントリーと呼ばれるローカルなJNDIツリーが存在し、web.xmlEJB参照として定義したEJB参照名がこのローカルなJNDIツリーにバインドされます。環境エントリーはjava:comp/env/というコンテキストにバインドされます。そうすることで、アプリケーション開発者はソースコードを修正することなくJNDIの参照先を変えられるので便利であるとされました。ちなみに、このようにEJB参照を使ってアクセスすることで上記ではというタグの指定を使うことで、同一earファイル中であればJNDIを経由せずに直接EJBを参照する最適化が可能になっています。
次に、EJBObjectを生成して呼び出すのですが、呼び出しもチェック例外の処理を行うと大変なので、J2EEパターンではBusiness Delegateと呼ばれるコンポーネントカプセル化すべきとされました。以下はBusiness Delegateの実装例です。

package hello.web;

import hello.ejb.Hello;
import hello.ejb.HelloHome;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.RemoveException;

public class HelloDelegate {

    private HelloHome helloHome;

    public HelloDelegate(HelloHome helloHome) {
        this.helloHome = helloHome;
    }

    public String sayHello(String name) {
        Hello hello = null;
        try {
            hello = helloHome.create(); // EJBオブジェクトを生成
            return hello.sayHello(name); // ビジネスメソッドの呼び出し。

        } catch (CreateException ex) {
            throw new RuntimeException(ex);
        } catch (RemoteException ex) {
            throw new RuntimeException(ex);
        } finally {
            if (hello != null) {
                try {
                    hello.remove();
                } catch (RemoteException ex) {
                    throw new RuntimeException(ex);
                } catch (RemoveException ex) {
                    throw new RuntimeException(ex);
                }
            }
        }
    }
}

そして、これらをコンポーネントを使ってサーブレットから以下のように起動します。

package hello.web;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloServlet extends HttpServlet {

    protected void sayHelloRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        
        
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        try {
            HelloDelegate delegate = new HelloDelegate(HelloServiceLocator.lookupHelloHome());
            String message = delegate.sayHello("Test");

            out.println("<html>");
            out.println("<head>");
            out.println("<title>Servlet HelloServlet</title>");  
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>" + message + "</h1>");
            out.println("</body>");
            out.println("</html>");
        } finally {            
            out.close();
        }
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        sayHelloRequest(request, response);
    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        sayHelloRequest(request, response);
    }
}

いかがでしょうか。詳細の理解はとにかく、実際、昔のJ2EEがあきれる程複雑だったという話のネタとして理解していただければ十分です。実際にここでやっていることはHello Worldの文字列を生成するEJBを呼びだすだけなのですが、これだけのコードや設定ファイルの記述が必要だったのです。しかも、これでももっとも仕様が単純なステートレスセッションBeanしか使っていませんでした。しかし、実際の開発ではエンティティBeanなどを使用するとさらに複雑になりますし、性能面などを考えるとリモート呼び出しのオーバーヘッドを避けるために、ファサードDTOなどのオブジェクトを作成し、値をまとめて転送したりする工夫が必要となり、さらにいろいろなクラスを作成することになります。

EJB2.xでは驚くべきことにさらに仕様が複雑化(2002年〜2006年)

J2EE1.4で標準化されたEJB2.1までは、基本的にこの複雑なプログラミングモデルに変化はありませんが、性能の問題を回避するために新たにローカルインターフェースという概念が登場しました。ローカルインターフェースを使うことでRMIなどの制約やオーバーヘッドがなくなるので多少は単純になるのですが、基本的な複雑さは変わらず、また、リモートとローカルを適切に使い分けるなど仕様はさらに巨大化しました。たとえば、ローカルホームをルックアップする処理は以下のように記述できます。リモートの場合との違いはルックアップの結果をPortableRemoteObjectを使ってナローイングする代わりに単純にキャストしている点です。

    public static HelloLocalHome lookupHelloLocalHome() {
        Context context = null;
        try {
            context = new InitialContext();
            return (HelloLocalHome)context.lookup("java:comp/env/ejb/helloLocal"); // 単純なキャストでOK

        } catch (NamingException ex) {
            throw new RuntimeException(ex);
        } finally {
            if (context != null) {
                try {
                    context.close();
                } catch (NamingException ex) {
                    throw new RuntimeException(ex);
                }
            }
        }
    }

一方、コンポーネントインターフェースの呼び出しは以下のように記述できます。ローカルインターフェースのためRemoteExceptionの処理は不要になっています。

package hello.web;

import hello.ejb.HelloLocal;
import hello.ejb.HelloLocalHome;
import javax.ejb.CreateException;
import javax.ejb.RemoveException;

public class HelloLocalDelegate {

    private HelloLocalHome helloLocalHome;

    public HelloLocalDelegate(HelloLocalHome helloLocalHome) {
        this.helloLocalHome = helloLocalHome;
    }

    public String sayHello(String name) {
        HelloLocal hello = null;
        try {
            hello = helloLocalHome.create();
            return hello.sayHello(name);

        } catch (CreateException ex) {
            throw new RuntimeException(ex);
        } finally {
            if (hello != null) {
                try {
                    hello.remove();
                } catch (RemoveException ex) {
                    throw new RuntimeException(ex);
                }
            }
        }
    }
}

なお、実際にEJB2.1の仕様で最高に複雑なのは今のJPAに相当するエンティティBeanの部分なのですが、ここではあまりにも複雑なため説明しません。興味のある方は昔のEJBの解説書や仕様書を調べてみてください。

EJB3.0におけるプログラミングモデルの劇的な単純化(2006年〜2009年)

こうした面倒な開発に対処するためにSpringやSeasar2のような軽量コンテナが発明された経緯がありますが、標準仕様としても、Java EE5とともに登場したEJB3の仕様でPojoやDIなど軽量コンテナの利点を採用することで開発の簡易化が図られました。実際、今回のHello EJBEJB3.0で作成するには以下のような手順で済みます。
まず、普通にHello EJBの機能を示すビジネスインターフェースをごく普通にJavaのインターフェースとして作成します。先ほどのEJB2.1までの例と違い、EJBLocalなどのインターフェースを継承したり、Homeインターフェースを定義したりする必要はありません。

package hello.ejb;

public interface Hello {    
    String sayHello(String name);
}

そしてEJBの実装クラスでは以下のように普通にインターフェースを実装すれば済みます。普通のPojoとの違いは@Statelessというアノテーションがついている点だけですね。

package hello.ejb;

import javax.annotation.Resource;
import javax.ejb.Stateless;

@Stateless
public class HelloBean implements Hello { //ビジネスインターフェースを普通に実装

    @Resource
    private String message;
        
    // ビジネスメソッド
    public String sayHello(String name) {
        return message + " " + name;
    }
}

以前のバージョンではJNDIを使って環境エントリーからメッセージ文字列をルックアップしていたのですが、@Resourceアノテーションにより、自動的にインジェクションできます。ただし、この場合以下のようなejb-jar.xmlの定義が必要になります。

<?xml version="1.0" encoding="UTF-8"?>

<ejb-jar xmlns = "http://java.sun.com/xml/ns/javaee" 
         version = "3.0" 
         xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation = "http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd">
    <display-name>EJB3App-ejb</display-name>
    <enterprise-beans>
        <session>
            <display-name>HelloBeanSB</display-name>
            <ejb-name>HelloBean</ejb-name>
            <ejb-class>hello.ejb.HelloBean</ejb-class>
            <env-entry>
                <env-entry-name>hello.ejb.HelloBean/message</env-entry-name>
                <env-entry-type>java.lang.String</env-entry-type>
                <env-entry-value>こんにちは</env-entry-value>
            </env-entry>
        </session>
    </enterprise-beans>
</ejb-jar>

サーブレットからの呼び出しも、従来の方式が全く嘘のように簡単に以下のように記述できます。ビジネスインターフェースを経由して普通に呼び出せますので、もはや、Service LocatorやBusiness Delegateなどのパターンを適用する必要はありません。

package hello.web;

import hello.ejb.Hello;
import java.io.IOException;
import java.io.PrintWriter;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloServlet extends HttpServlet {

    @EJB
    private Hello hello; // EJBをDIによりインジェクション
    
    protected void sayHelloRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        
        String message = hello.sayHello("Test");
  
        try {
            out.println("<html>");
            out.println("<head>");
            out.println("<title>Servlet HelloServlet</title>");  
            out.println("</head>");
            out.println("<body>");
            out.println("<h1>" + message + "</h1>");
            out.println("</body>");
            out.println("</html>");
        } finally {            
            out.close();
        }
    }

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        sayHelloRequest(request, response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        sayHelloRequest(request, response);
    }
}

EJB3.1でようやく軽量コンテナの生産性に追いついた(追い越した?)(2010年〜)

Java EE5で登場したEJB3.0により、PojoとDIを使ってEJBを開発できるようになり、このように表面的にはSpringなどと同様に開発できるようになったように見えます。しかし、実際には

  • 問題1 EJB開発には重たいアプリケーションサーバーが必要
  • 問題2 インメモリーの埋め込みサーバーの利用が標準化されておらず、単体試験の自動化が困難
  • 問題3 EJBを利用する場合にはEARファイルへのパッケージ化が必要
  • 問題4 EJBは実は軽量ではなく、インスタンスプールなどサーバーリソースを消費する
  • 問題5 以前のコンポーネントモデルを踏襲しており、JNDIの名前空間なども統合されていない

最初の二つの問題はJava EEサーバーが重くてテスト不能というイメージはもう過去の話かもしれない - 達人プログラマーを目指してでも説明したように、現在ではGlassfishなどの軽量のサーバーが登場したことで既に解決しています。ここでは、残りの問題がJavaEE 6のEJB3.1でどのように解決されているのか見ていきます。

EJB3.1はwarファイル中に格納できる

EJB3.1仕様のEJBはwarモジュール中のWEB-INF/classesディレクトリーに格納したり、WEB-INF/libフォルダ中のjarファイルに含めることができます。したがって、EJBを利用するという理由のためだけで独立したejb-jarファイルとearファイルの作成を行う必要はなくなります。以前Java EEのearファイル形式はMavenの天敵 - 達人プログラマーを目指してで書いたようにearファイルのビルドは面倒なところがあり、またクラスローダーの階層も複雑だったため、これだけでも大幅な簡易化と開発生産性の向上が期待できます。
なお、warファイルにEJBを格納した場合はJAX-RPCやEJB2.1までのエンティティBeanといったレガシー機能は利用できませんが、リモート呼び出しなどを含めてその他の機能は利用することができます。

本物のPojoをDI管理可能にする管理Beanの概念の登場

EJBは見かけ上はPojoの形で開発できますが、実際には従来通りの重量コンポーネントであることには変わりがありません。実際ステートレスセッションBeanであれば、EJBごとにサーバーにインスタンスのプールが構築されるなど、それなりのリソースを消費します。しかし、実際にはEJBコンテナの機能は不要だけれども、DIなどのサービスを利用したいことが多くあります。ここも従来JavaEE 5までは軽量コンテナに対して不便な点だったのですが、JavaEE 6では管理Beanという概念が定義されています。特に、@javax.annotation.ManagedBeanをPojoに付けることで

  • @Resourceや@EJBを使ったDI
  • @PostConstruct、@PreDestoryなどのライフサイクルコールバック
  • インターセプタの適用によるAOP

などの基本的なDIコンテナのサービスを受けることができます。仕様では従来のEJBは管理Beanの一種であるとして定義されているため、図示すると以下のような関係となります。

なお、大混乱に陥っているJavaEE 6のアノテーションに関する使い分けについて - 達人プログラマーを目指してでも書いたように、非常に勘違いしやすいのですが、faces-config.xmlか@javax.faces.bean.ManagedBeanで定義されるJSFの管理Beanの概念は、@javax.annotation.ManagedBeanで定義される管理Beanとは別の概念のため混同しないように注意が必要です。@javax.annotation.ManagedBeanで定義されたBeanをJSFの管理Beanとして利用することはできません。
実際、以下のような形で管理Beanを定義すると

package hello.web;

import javax.annotation.ManagedBean;

@ManagedBean("sample")
public class SampleBean {
    
    public String sample() {
        return "test";
    }
}

サーブレットや他のEJB(あるいは一般に管理Bean)に対してインジェクションすることが可能です。

@WebServlet(name = "HelloServlet", urlPatterns = {"/hello"})
public class HelloServlet extends HttpServlet {

    @Resource
    SampleBean sample;

... 
}
CDIが有効化されたモジュールでは結局ほとんどすべてのBeanが管理Beanに

今まで説明してきたJava EE6の管理Beanですが、実際には以下のような大きな制約を持っています。

  • ライフサイクルが単純なprototype Bean的なスコープしか持たない
  • モジュールごとにJNDIの名前空間が分断されてしまう(EL式などで扱いにくい)
  • JSFの管理Beanとして利用できない

まず、最初の問題ですが、@javax.annotation.ManagedBeanで定義されたBeanはちょうどSpringのprototypeスコープのBeanと同様にJNDIからルックアップしたり、インジェクションされる度に別々のインスタンスが生成されます。シングルトン的に共有したり、セッションなどの特定のスコープで保持することができません。
2番目の問題はwarのみを使うプロジェクトでは問題とならないのですが、earを使うような場合に問題となります。例えば、warファイル中のサーブレットからejb-jarに格納された管理Beanをインジェクションするためには、わざわざ以下のようにルックアップ先を指定する必要があるのです。

    @Resource(lookup="java:app/EJBjar名/sample")
    SampleBean sample;

そして、一番大きな問題はその名前にも関わらずJSFの管理Beanとして利用できないことです。
もともと、JBoss Seamはその名前の通り、EJBJSFの管理BeanなどあらゆるPojoをBeanとして扱い、全レイヤーを継ぎ目なくアクセスするという思想があります。Seam作者であるGavin King氏が中心となって策定されたCDI(JSR-299)の仕様では、この思想にしたがって一部例外を除き結局ほとんどすべてのPojoを管理Beanとして統一的に扱うことができるようになっています。さらに、セッションや会話などのスコープも定義することができるようになっています。
JavaEE 6では各モジュール中にbeans.xmlファイルが含まれているとCDIの機能が有効になることになっていますが、この場合は結局以下のような感じになるということですね。

CDIを有効にした場合、EJBもその他のPojoも以下のように統一的にアクセスできるようになるだけでなく、さまざまなスコープ中に格納してEL式から参照することも簡単にできるようになります。なお、この場合にJSR-330で別途規定されているDIのアノテーションを利用することになります。*2

@WebServlet(name = "HelloServlet", urlPatterns = {"/hello"})
public class HelloServlet extends HttpServlet {

    // EJBもPojoも同様に統一した形でインジェクションできる。
    @Inject
    private Hello hello;
    
    @Inject
    private SampleBean sample;
...
}

この場合、EJBは従来のようにコンポーネントと考えるのではなく、トランザクションやプールなどの特殊なアスペクトのかかった管理Beanに過ぎないと考えた方がしっくりきます。そして、従来のようなJNDI名を使った環境エントリのようなJava EEの複雑な仕掛けを一切忘れることが可能になります。
そうすると、従来のEJBコンポーネントモデルがまったく蔑ろにされているようにも思われるのですが、

  • 従来のシステムとの互換性を考える必要がある。
  • 要件によりモノリシックなWebアプリケーションではなく、疎結合コンポーネントモデルが適切

などの場合には、CDIのオプションをはずして、従来通りのコンポーネントモデルで開発することができます。このあたりは、アーキテクトの判断で個別に適切に使い分ける必要があると思います。*3

まとめ

以上、この10年間のJ2EEおよびEJBのプログラミングモデルの進化について簡単に振り返りました。こうした進化が一昔といわれる僅か10年の間に急激に起こったため、ちょっとでも技術から離れてしまっているとこのような劇的な変化があったということに気づきもしないかもしれません。特に、プログラミングをしない上流のSEやコンサルの人は、J2EE関連のシステムに関わっていてもこうした事実をまったく知らないという人もいるのではないでしょうか。そして、いまだに当時の古いJ2EEの仕様に基づいて作成された社内標準フレームワーク上でアプリケーションを作成しなくてはならないというチームも多くあるのではないでしょうか。
一口にEJBと言っても、ここで紹介したようにまったくプログラミングの方法や考え方がバージョンによって異なります。特にJava EE6で利用できるようになったCDIは従来のコンポーネント中心のモデルと比較してパラダイムシフトといえるくらいの大きな変化と言えます。
Java EEは重くて使えないと信じている人も一度最新のCDIを試してみるとその生産性の高さを実感できると思います。
なお、(私もすべて完読できていないのですが)参考書を紹介させていただきます。
- The Java EE 6 Tutorial

Java EE 6 Tutorial, The: Basic Concepts (Java Series)

Java EE 6 Tutorial, The: Basic Concepts (Java Series)

  • 作者: Eric Evans, Ian Gollapudi, Devika Haase, Kim Srivathsa, Chinmayee Jendrock
  • 出版社/メーカー: Prentice Hall
  • 発売日: 2010/08/24
  • メディア: ペーパーバック
  • クリック: 12回
  • この商品を含むブログ (1件) を見る
以下はCDIを管理Beanとする前提で説明が書かれた古くからあるJSFの参考書の改訂版です。
Core JavaServer Faces (Core Series)

Core JavaServer Faces (Core Series)

EJB3.1の本も出版されています。
Enterprise JavaBeans 3.1: Developing Enterprise Java Components

Enterprise JavaBeans 3.1: Developing Enterprise Java Components

以下はGlassfishを使ったJava EE6の全般的な解説がされた良書ですが、残念ながらCDIに関する記述が欠けています。
Beginning Java EE 6 with GlassFish 3 (Expert's Voice in Java Technology)

Beginning Java EE 6 with GlassFish 3 (Expert's Voice in Java Technology)

また、CDIに関してはJSR-299の仕様書の他、JBoss Weldのマニュアルも参考になります。
http://seamframework.org/Weld/Documentation
以下のサイトも参考になりますね。
Seam3の概要 | Think IT(シンクイット)
あと、Twitter上で#javaeejpというハッシュタブ上でJavaEEに関する議論をしたいと思っていますので、ご興味のある方は是非参加してください。最新バージョンにかかわらず、ここで紹介した古いバージョンの改善方法や移行方法などを含めて議論できればと思います。

*1:多くの場合リモート呼び出しやエンティティBeanが原因

*2:もともとはCDIでは全然別のアノテーションが使われる予定だったが、最終的にJSR-330のアノテーションを利用することで統一された経緯がある。Java EE 6 に調和する依存性注入

*3:それにしても、これだけの仕様の変化に追随して、開発時に適切に判断する必要があるアプリケーションアーキテクトとは実に大変で割に合わない仕事だとは思いますね。

SI業界(日本)のJavaプログラマーにはオブジェクト指向より忍耐力が求められている?

私自身は10年以上も前(JDK1.1の頃)にSJC-Pの認定を取って以来、Javaプログラミング関連の認定試験は受けていないのですが、昨日たまたまネットを検索して、SJC-Pとは別にJavaプログラミング能力認定試験という試験が存在していることを知りました。結構メジャーな認定試験のようですので、現役のJavaプログラマーJavaプログラマーを目指している学生さんで、今後受験に向けて勉強されている方々も多くいらっしゃるのではないかと思います。
試験は難易度に応じて3級から1級までランクが分かれており、2級まではJava言語の知識に関する筆記試験ですが1級の試験では実際のプログラムの修正を行う能力が実技試験として課せられます。試験範囲は以下で公開されています。
Javaプログラミング能力認定試験(試験範囲)
私は(自分で言うのも変ですが)、Javaプログラミングについてはこの道15年近くのキャリアーがある「ベテランJava PG」なので、最上級の1級に余裕で合格できなくては恥ずかしいと思われますが、この1級の試験については実技試験で機能追加を行うベースとなるプログラムのソースコードと設計書が正規のサイトで公開されていました。いきなりぶっつけ本番で仕様書を渡されてゼロベースでプログラムを書くということではなく、事前にソースコードを十分読み込んだ上で試験に臨むことができるようです。もちろん、プログラムのどの部分が改変対象となるのかというところについては公開されていません。(ちなみにサンプル問題を見る限り、改変は機能修正や機能追加であり、リファクタリングということではもちろんないです。改変部分の変更定義書の記述も必要。この問題が既存の設計をあるべきオブジェクト指向設計リファクタリングせよということであれば、非常に難易度が高いけど、チャレンジしてみたいと思う良問となるのですが・・・。)
私としてはちょっと興味があったので、以下から1級の試験問題のサンプルを実際ダウンロードして、試験で改変する対象となるプログラムの設計書とコードを読んでみました。
Javaプログラミング能力認定試験(試験サンプル)
もともと、この試験の目的としては

Javaに関する基本知識を有し、オブジェクト指向に基づくアプレットやアプリケーションプログラムを作成できる能力を認定します。

オブジェクト指向に基づく分析・設計(UML)により業務システムの流れを把握し、変更仕様に従ってプログラムの保守ができる能力を有する。なおUMLの表記はユースケース図、シーケンス図、クラス図などの基本的な知識を有する。

ということになっていて、あくまでもオブジェクト指向言語の試験であることが明記されているようですが、ダウンロードした設計書とコードを見ると、純然たる手続き型構造化のクラス設計で作られていることが一目瞭然でした。まず、個々のクラスの単位が以下のように処理の単位、すなわちCOBOLの主プログラム、副プログラムと同じ考え方で分割されています。このことは以下のようにクラス名が通常のように名詞ではなく、機能を表す動詞となっていることからも明らかです。

クラス名 処理概要
Manage 人材派遣管理メインクラス(機能選択)
DContents 機能表示
GChars 入力受取り
GGList 業種リスト作成
JDelete 人材情報削除
JDisplay 人材情報表示
JInput 人材情報追加
JUpdate 人材情報更新
KDelete 稼働状況削除
KDisplay 稼働状況表示
KInput 稼働状況追加
SInput 検索情報入力(検索条件指定)
SOutput 人材検索

これがStrutsのアクションのような感じでTransaction Scriptとして作られているなら良いのですが、そういうものではなく、純粋にCOBOLプログラムのような設計となっています。そして、クラスの抽出の方法のみでなく

  • ファイルから読み込んだレコードデータをオブジェクトに入れず、すべてString[ ]やString[ ][ ]などの配列として扱っている。(コレクションクラスはほとんど使われていない)
  • 単にグローバルメソッドのstatic importの目的のためだけにクラスを継承している。ポリモーフィズムは一切活用されていない。
  • privateやpublicなどのアクセススコープの指定は(mainメソッド以外)一切ない。
  • メインクラスからサブクラスまで処理単位の入れ子モジュールとして構造化されている
  • ファイルの読み込み(データアクセス)、画面出力、ロジックが混在して記述されており、アーキテクチャー上まったくレイヤー化されていないし、MVC構造といったものもない。
  • FileReaderなどの低水準のクラスを直接アプリケーション層のクラスが扱っている。(例題とは言え、例外処理やクローズ処理がいい加減過ぎる。)

などなど、これが資格試験の問題なのかと目が点になってしまうような驚くべき設計のコードとなっています。まさに、以前にJava EEや.NETはCOBOLやVB6よりも本当に生産性が高いか?で指摘したように、COBOLの方がよほど生産性が高いのではないかと思われるような設計となっています。
一応出題者の名誉のためにフォローしておくと、この試験の目的は
あくまでも取得した後に実社会で価値のある認定試験であること
ということのようなのです。よって、日本のSI業界でJava PGとして仕事をするためには、オブジェクト指向的にきれいなあるべき姿でコーディングできるスキルではなく、このようにオブジェクト指向をまったく理解していない上流のSEが作成した異常な設計書に忠実にしたがってコードを書き、また、その複雑なスパゲッティコードを長期にわたってメンテナンスする根性と忍耐が最重要のスキルとして試験で試されているということなのかと私は理解しました。(そういうことであれば、私はこの試験に合格する自信がないですが。)
運良く私がPGとしてかかわったプロジェクトではここまでひどい設計を押し付けられたことは無いのですが、仕事に役立つとされる資格試験の最上級の試験で、このような問題が出題されているという事実をあらためて知り愕然としました。
書籍の例題プログラムやOSSで公開されているソースを作成したプログラマーは、それなりにオブジェクト指向を理解している優秀なプログラマー達なのですから品質の極端に悪いコードを見かけることはめったにありません。その一方で、以前にも(グルーポンのおせち事件を受けてSI業界が本当に教訓とすべきこと)で指摘したように、SIerが開発したシステムのソースコードがネット上に公開されたり、議論されたりすることは当然ですがほとんどありません。しかしながら、記述されているコードの分量から言えば、日本SI業界においてネットや書籍で多くの人から評価されないJavaコードの大半は、この試験の問題が象徴しているような低品質のものが多いということがやはり事実ではないでしょうか?
OSSなどで活躍されている一流のプログラマーの方々は、こうした業界の有り様には興味がないのでしょうか?実際、

などという議論がネット上で盛んに行われていますが、こうした一流PGの方々は、業界の一般のPG(私を含めて)とはまったく別次元の遠く離れた世界で生きているのではないかと感じてしまいます。でも、少なくとも20年は遅れた開発手法が蔓延している、日本のSI業界のPGを取り巻く環境の惨状を、今後もはたして放置しておいてよいものなのでしょうか?確かに、このような時代遅れで低レベルのプログラムを開発しているのは自分とは関係の無い、ブラック会社などSI業界の底辺の一部の世界で行われていることなのであると単に無視してしまうことは容易です。しかしながら、一般の職業Javaプログラマーの大半は、旧態依然としたSI業界の枠組みの中で日々働いています。このような問題をずっと見過ごしていては、今後もSI業界を支える多くのプログラマーの価値が低く評価され続け、無益かつ過酷な単純労働を長時間強いられることになる*1のではないでしょうか?技術者として自分の興味のある最新技術を追いかけるのもよいですが、私としてはこういった業界の問題があたかも存在しないことのように無視され続けるのは非常に残念に思います。
なお、そもそもこの例題プログラムで行っていることは、単純なタブ区切りのファイルに対するCRUD処理を行うものです。今の時代にこのようなシステムの構築に最低限RDBを使うのが当然ということはおいておいたとしても、設計書に記載されているシステムの仕様自体、ビジネス上ほとんど価値のない事をやっているわけです。それにもかかわらず、正しい設計であれば本来不要な、大量のコードや設計書の記述が必要で、ちょっとした機能追加を行うだけで最上級の試験問題のネタになってしまうくらいに大変な作業が要求されることになるのです。プログラミング技術のわからないSEの書いた不適切な設計書に従ってこのような生産性の低いコーディング作業を行うのであれば、確かにPGは付加価値の低い単純労働であり、その仕事に対する労働対価は非常に低いということはそのとおりであり、私としても否定する余地がありません。
ところで、百聞は一見にしかずです。機会があれば、この例題プログラムをあるべき形にリファクタリングした結果と、そのリファクタリング手順を皆さんにお見せできればと思います。そうすれば、生産性を向上させ、PGとして付加価値の高い仕事をする上で、プログラムの正しいアーキテクチャー設計がいかに大切であるかということをご理解いただけるものと思います。

*1:問題の本質は単に3Kとか7Kとかいうことではなくて、PGとしてのスキルが正当に評価されることなく、単純労働を強いられるということにあります。

アバンダンウェアで懐かしい過去の時代にタイムスリップしてみる

皆さんはアバンダンウェア(Abandonware)という用語をご存知でしょうか?
wikipedia:アバンダンウェアにあるように、

著作権者が既に販売をやめたりサポートしていないソフトウェア、あるいは様々な理由により、誰が著作権者であるか不明なソフトウェアを指すために用いられる語である。

ということで、既に商品としての価値が無くなっていたり、著作者が既に不明になっていたりしているような非常に古いソフトウェアを指す用語です。*1

  • コンピュータ技術の進歩はあまりも急速なため、通常多くの国で認められている著作権の消滅期限より遥かに前に、商業的な価値がなくなる可能性が高い
  • ソフトウェアはインターネットなどを通して簡単にコピーが出回ってしまう

ということから、コンピューターソフトウェア固有の現象と思われます。もちろん、

この用語は法的な意味を持たない。すなわち「アバンダンウェア」と呼ばれているソフトウェアの複製物を、著作権者の許諾なしに取得することが法的に許されているわけではない。著作権者がその著作権を放棄するなどの理由により著作権が消滅しない限り、全てのアバンダンウェアは著作権の保護期間が経過するまで、著作権の対象となる。

ということで、アバンダンウェアの配布やダウンロードを行うことは限りなく黒に近い違法行為であると考えられ、決して声を大にして他人に勧められる行為ではないことは確かなのですが、まあ、個人で楽しむだけなら許容されるのではないでしょうか。(その点、くれぐれも個人の判断で行っていただきますようお願いします。)ここでは、アバンダンウェアを使って、私が始めてプログラミングを勉強した頃の時代(年齢がばれますが)にタイムスリップしてみたいと思います。

MSXパソコンのシミュレータで遊ぶ

私が始めてパソコンと呼ばれるものでプログラミングをしたのは小学校の中学年の頃だったと思います。今は亡き私の祖父*2が趣味で電子音楽の制御に使っていたYAMAHAMSXパソコン(おそらくCX5という機種だったと思う)を譲ってもらったのがきっかけでした。当時は私は英単語もまったく読めませんでしたし、ブラインドタッチもできなかったのですが、雑誌*3などに載っていたBASICのプログラムリストを必死に打ち込んでゲームを作成したのを覚えています。以下の動画の中に私の打ち込んだ雑誌のゲームもあってすごく懐かしいです。

幸運なことに、現在では非常に優秀なMSXエミュレーターをフリーで簡単に入手することができます。
MSX エミュレータ|エミュポータル
こうしたエミュレータを使うと、BASICやマシン語などで実際にプログラムを作成してみることができます。
たとえば、

のように順番に円を描くプログラムはBASIC言語で以下のように記述すれば簡単にできます。(今では信じられないことですが、変数名は大文字小文字を区別なしの英数字2文字まで、GOTO先も行番号指定のみでラベル名が使えせんでした。当然ローカル変数というものはないです。)

なお、以下の雑誌には公式MSXエミュレーターだけでなく、MSX用のOSであるMSX-DOSMS-DOSではない!)やZ80のマクロアセンブラやCコンパイラが付属しているので、いろいろと楽しめます。MSXプログラミングの参考書などはヤフオクなどで今でも結構出回っています。

MSX MAGAZINE永久保存版3

MSX MAGAZINE永久保存版3


もし、私に天才的なプログラミングの才能があれば、すごい面白いゲームを作成して雑誌に投稿していたりしていたのでしょうが、MSXに関しては当時は結局そこまで使いこなすことはなく、結局ゲーム機*4として終わりましたね。
Z80マシン語などはJavaプログラマーでも教養として有用なので、時間のあるときに一度勉強しておくのも良いでしょうね。
はじめて読むマシン語―ほんとうのコンピュータと出逢うために

はじめて読むマシン語―ほんとうのコンピュータと出逢うために

Turbo Pascal*5OOPに初めて出会った時の感動を再度味わう

それからしばらく、プログラミングからは遠ざかっていたのですが、私が再びプログラミングの面白さに目覚めたのは大学の授業でPascal言語を習ったことがきっかけでした。*6学校では構造化型言語ということで学び、初めて関数、手続き、ポインターなどの基本概念を学んだのですが、より深く勉強しようと自分で購入したTurbo Pascal6というパッケージには、Turbo Visionと呼ばれるアプリケーションフレームワークが付属していました。それに付属していたユーザーズガイドのなかにあるオブジェクト指向プログラミング入門というのが非常に分かりやすいく書かれていたと記憶しています。残念ながら、そのマニュアルは紛失してしまったのですが、英語版であれば、以下からダウンロードできます。
E-Book | Turbo Pascal 5.5 Object Oriented Programming Guide
今でこそアメリカなど海外ではオブジェクト指向プログラミングは常識として浸透しているみたいですが、当時(20年前)は一般のPC環境でようやく利用されるようになった頃であり、多くのプログラムはオブジェクト指向言語を使って書かれていませんでした。このマニュアルを読むと、オブジェクト指向という考え方*7がいかに画期的ですばらしいものなのかという作者の意気込みが感じられます。オブジェクト指向プログラミングの威力を最もはっきりと感じるのは、やはり、マウスやウィンドウ、メニューバーを使うようなGUIのプログラミングを行う時です。当時はWindowsのようなグラフィカル環境はまだ一般的ではありませんでしたが、この製品に付属していたTurbo Visionというアプリケーションフレームワークを利用することで、MS-DOS上で簡単に「GUI」っぽいアプリケーションを開発することが可能でした。

ここで、非常に感激したことは、Turbo Pascalの提供するIDE環境そのものがTurbo Visionというフレームワーク上に構築されていたという事実ですね。実際、上記のサンプルアプリケーションの画面とIDEの画面は非常に似通っていました。

MSXのBASICでは「円を描く」「線を引く」という一個一個の基本的な処理を呼び出してプログラミングしていたものが、TWindowをNewしてデスクトップに貼り付けることで、自分からひとりでにリサイズやドラッグなどのイベントに反応するウィンドウが画面に表示されるようになるのです。「なんと効率的で再利用性の高いプログラミング技術があるのだろう!」私は心の底から感動したのを覚えています。実際、自分のような平凡な人間が少ないコードの記述で見栄えと操作性のよいアプリケーションを作成できてしまうのですから、当時としては実際すごいことでした。自分は当時完全に独学でOOPを勉強した*8ので、OOPのすごさを実感として分かったといえるレベルになるまでに2〜3年はかかったような気がしますが。なお、Turbo Pascalは完全に16ビットのDOSコンパイラなので、64ビットWindows環境では仮想環境上でないと動作しません。しかし、現在でもFree PascalというフリーのコンパイラーがOSSとして開発されています。Turbo VisionについてもFree Pascal上に移植されています。
Free Pascal - Advanced open source Pascal compiler for Pascal and Object Pascal - Home Page

ちょっと思ったこと

ちょうど私の世代(団塊ジュニア世代)というのは、以上のような手続き指向の環境からオブジェクト指向の環境への移行をまさにリアルタイムに肌で体験することができた幸運な世代であったのかもしれないと思いました。それより古い世代だとCOBOLFORTRAN、BASICといった手続き型全盛の時代になってしまいますし、JavaWindows以降の世代では、オブジェクト指向は日常既に空気のようなあたりまえの存在になってしまっており、そのありがたみをまったく感じなくなってしまっていますから。オブジェクト指向の存在意義がよく分からないという人は、ここで紹介したようなアバンダンウェアを使って、オブジェクト指向が登場してきた時代にタイムスリップしてみることで、「あ、そういうことだったのか」という気になるかもしれませんね。よくオブジェクト指向の入門書で書かれているように「哺乳類を継承して犬や猫になる」という抽象的な説明を何度も聞くより、Turbo Visionでウィンドウを開くプログラムを作ってみるのがOOPの威力を感じるのに非常によい手段だと思います。まさに百聞は一見にしかずということで。*9

*1:一般にAbandonwareというと多くはROMデータとして違法に出回っている昔のゲームのことを指すことが多いようです。古いゲームは現在でも商業的な価値が高いものが多いため、特に問題が大きいと思われます。

*2:文字通りコンピュータおじいちゃんという感じの人でした。80歳を超えて亡くなるまでパソコンやデジタル家電に興味を持っていました。このgeekな趣味は隔世遺伝で私に受け継がれているようです。

*3:コードリストが入手したくてオークションなどしばらく探していたのですが、今ではなかなか出品されないですね。

*4:当時みんなが持っていたファミコンは勉強しなくなるからという理由で買ってもらえなかった。

*5:Turbo Pascalは以下のようなサイトでダウンロードできます。http://vetusware.com/

*6:私は情報専門の学科は卒業していませんが、プログラミングの授業としてPascal言語は必修科目でした。

*7:当時はGoFデザインパターンも知られていない時代だったので、継承を多用しすぎるなど現代のオブジェクト指向プログラミングのベストプラクティスから考えると不適切な設計もありましたが。

*8:当時はヒープとか動的変数割り当てといった基本的なことをよく理解していなかった。

*9:Turbo Pascalの少し後に登場したVBだとあまりにもブラックボックス化されてしまったため、オブジェクト指向がちょっと見えにくくなってしまっています。

Java EEや.NETはCOBOLやVB6よりも本当に生産性が高いか?

プログラミングと設計は本来切り離せないものなのではがすごい反響だったのですが、結局この記事で私が言いたかったことは、

  • Java EEなどの現代的な開発環境はCOBOLなどの古い言語を使った開発とは根本的に設計の手法が異なる
  • 多くの現場では未だに古い設計手法を使っているため、オブジェクト指向などの最近の開発環境のメリットが活用できず、低い生産性にとどまっている。

ということに要約できると思います。ただし、どうして、Javaではオブジェクト指向で開発しないといけないのか、どうして昔ながらの伝統的なやり方を改め、新しい設計手法を採り入れないといけないのかと疑問を持たれた方もいらっしゃるかもしれません。ここでは、開発手法と生産性の問題について、もう少し掘り下げて検討してみたいと思います。

レガシー言語の生産性

最近のCOBOLでは、オブジェクトやスタック変数すら使えますが、ここではCOBOL85のような伝統的な手続き指向の言語についてちょっと考えてみます。最近はCOBOLで書かれたプログラムを読んだことがまったくないプログラマーも珍しくないかもしれませんが、固定長ファイルの読み書きや帳票の出力など、もともとCOBOLの得意分野については、下手なJavaプログラムよりよほど生産性が高いのではないかとすら思えます。たとえば、固定長ファイルを処理するロジックなら大体以下のような感じで記述できます。

       IDENTIFICATION DIVISION.
       PROGRAM-ID. SAMPLE.
      * 環境部
       ENVIRONMENT DIVISION.
        INPUT-OUTPUT SECTION.
         FILE-CONTROL.
           SELECT DATA-FILE
               ASSIGN       TO  infile
               ORGANIZATION IS  LINE SEQUENTIAL.
      * データ部
       DATA DIVISION.
        FILE SECTION.
        FD  DATA-FILE.
        01  DATA-RECORD.
            02  USER-RECORD.
                03  USER-CODE    PIC X(4).
                03               PIC X.
                03  USER-NAME    PIC X(20).
                03               PIC X.
                03  USER-AGE     PIC 9(3).
      * 手続き部
       PROCEDURE DIVISION.
           OPEN INPUT  DATA-FILE.
       LOOP-POINT.
           READ DATA-FILE AT END GO TO TERM-PROC

... 各レコードの処理を記述

           GO TO LOOP-POINT.

       TERM-PROC.
           CLOSE  DATA-FILE.
       END PROGRAM SAMPLE.

確かに、最近のスクリプト言語とは比べ物にならないくらい冗長な記述ではありますが、固定長ファイルを処理するという本質的な部分についてみると

  • ファイルの中身が特定の変数に自動的にバインドされている
  • 変数の型変換も自動的に処理される

などを考えると、最近Javaでようやく利用できるようになったアノテーションを使ったBeanバインディングと大差ないようにも思えます。見方によってはPIC句による文字種や桁数の宣言はソース内部に記述されたメタデータといえなくない気がしますし。また、決まりきった部分をテンプレートとして流用すれば*1、可変部分の記述はシンプルで、IoC*2のように見えなくもないです。そのほか、COBOLでは固定小数演算で誤差のない金額計算などが得意といった特徴もあります。とにかく、COBOLでは細かい部分の設計としてSEがデータの型桁と処理ロジックを決め、アーキテクチャー的な部分は大規模なプログラムのモジュール分割や呼び出し順序の設計をすれば、基本的にはひたすらコードを書き下すだけです。*3インターフェースとかデザインパターンなどは本当に考える余地がないのです。変数も基本的にグローバルな静的変数となります。同じ第三世代の手続き型言語といってもCやPascalのような柔軟性の高い汎用言語しか知らないとCOBOL独特のこうした特徴は理解しづらいでしょう。COBOLというのは事務処理フレームワークが内蔵された言語と言えます。キーワード(予約語)が500種類*4もあるという点もこの事実を物語っています。
同様に、VB6などのツールはGUIを作成するツールとしては生産性が非常に高く、データアクセス用の部品を使えばCRUD処理などは簡単に実現できてしまいます。このようなことを考えると最近では開発ツールの使い勝手が向上したり、PCの性能が向上したりしたことによる生産性の向上はあるのですが、言語自体の生産性は昔からそれほど代わっていないのではないかという意見も当然出てくると思います。だから、最近でも

などといった意見も時々出てくるのだと思います。

どうしてオブジェクト指向言語なのか?

それではどうしてJavaなどのオブジェクト指向言語が現在では一般的になっているのでしょうか?こうしたことは書籍やネットなど、さまざまなところで既に多くの人によって語られていると思いますが、結局

  • 部品化によって再利用性が向上する
  • インターフェースと実装の分離などにより、変更の容易な保守性の高いプログラムが作成できる
  • 複雑なロジックをオブジェクト内に隠蔽することにより、巨大なプログラムを作成できる

ということになるのだと思います。実際、Windowsや3Dを駆使したゲームなど、きわめて複雑なプログラムをオブジェクト指向抜きで実装することは現実的でないと言ってよいと思います。業務システムでもオブジェクト指向言語を使って適切な設計をすることで生産性や保守性の高いシステムを構築することができるのです。ただし、ここで大切なことはオブジェクト指向言語を使ったからといって自動的にオブジェクト指向のメリットが得られるのでは決してないことです。Java言語を使っても、デザインパターンドメインモデリングなどを適切に利用して、正しい設計ができて初めて上記のメリットが得られるのであって、従来と同じ発想でデータ構造とロジックだけ考えていたのでは、高い生産性を享受することは絶対にできません。それどころか、固定長ファイルを単にバッチ処理するロジックを(クラスライブラリーやフレームワークを一切使わず)Java言語で手続き的に実装したらCOBOLよりもかえって生産性が下がるという危険性すらあるのです。

SIerJavaフレームワークの問題点

つまり、Javaなどのオブジェクト指向言語では適切にオブジェクト指向設計できる高スキルのプログラマーなら高い生産性を享受できるのに、初心者だと多くの場合COBOL以下の生産性しか得られない。ここに問題の本質があります。
SIerの多くはCOBOL時代からの伝統的な慣習からか、

プログラマー = PG = 初級レベルのエンジニア = 下流 = 単価が低い

という構図で考える傾向があります。だから、一般的に日本のSIerの作ったJavaフレームワークというのは、「いかにプログラマーに頭を使わせないか」という点に最大限の注意を払っているように思います。実際、今まで私が見てきたSIerフレームワークは以下の2通りのパターンのいずれかです。

  • なんちゃってパターンの使用を強制する。XXDto、XXAction、XXLogicなどのように同じ接尾辞が付く多数のクラスを作らせるのは典型的なケースです。(侵略的なフレームワーク - 達人プログラマーを目指して)ここではどうしてパターンを使うのかということは*5どうでもよくて、とにかく規約として一連のクラスの作成を強制させます。そうすることでオブジェクト指向している気分になっているのかもしれませんが。
  • 何でもかんでもとにかく自動生成させたがる。特にExcelなどの表から大量のクラスを自動生成させるなど。たいていそのようにして生成されたクラスはゴミで保守も大変なものになりがちです。

結局、たいていの場合SIer製のフレームワークというのは、プログラマーオブジェクト指向設計する自由を奪っているのであって、できるプログラマーの能力を殺してしまうのですが、もっとも重要な事として先に述べたとおり、Java開発の生産性をCOBOLVB以下に制約しているということすらいえる可能性があるのではないでしょうか?もともとの思惑としては、オブジェクト指向を使わずにCOBOLと同じ発想で設計していても、新しい言語なのだからJavaの方が多少なりとも生産性が高いだろうという期待があるのかもしれませんが、そんなことは決してないと思います。むしろ、本来の使い方とはかけ離れた不自然な設計を強制することで、多くの場合COBOL以下の生産性しか得られていないというのが実態な気がします。*6

最近のOSSフレームワークは何が違うか?

SIerフレームワークと比較して、Seasar2Spring Frameworkなど最近のOSSフレームワークは、プログラマーの自由を奪わない*7(侵略的でない)ことが特徴とされています。つまり、基本的に「優秀なプログラマーの優秀なプログラマーによる優秀なプログラマーのための」フレームワークとなっているのです。だから、必ずしもオブジェクト指向デザインパターンがわからないような初心者にやさしいかというと必ずしもそんなことはありません。しかし、天才プログラマーでなくてもある程度これらの基礎をきちんと勉強しているプログラマーが使うと、高い生産性を享受できるのが特徴です。きちんと勉強しさえすれば、平均的なプログラマーであっても天才プログラマーの考えた仕組みを取り込むことができるのです。
ところで、SIerフレームワークであっても最近はこれらのOSSフレームワークを取り込んでいることが多くあります。しかしながら、私の知る限りにおいて現状では、従来のSIerフレームワークと大きく変わるものではないと思います。基本的にプログラマーは初心者という前提で作られていることは以前とかわらず、OSSフレームワークプログラミング言語が本来持っている柔軟性や良さを殺しているケースがほとんどであると思います。

生産性とプログラマーのスキル

以上の説明をもとに、生産性とプログラマーのスキルとの関係をグラフに表すとだいたい以下のような感じになるのではないでしょうか。(あくまでも定性的な傾向を私の個人的な主観で表現したものです。)

SIerは一般的に領域①で大量のプログラマーを低い単価で雇って仕事をさせようと考える傾向があります。思惑としてはJavaフレームワークを導入することでCOBOLよりも高い生産性を確保したいということがありますが、多くの場合は、COBOL以下の生産性でプロジェクトが失敗したり遅延したりします。成功したといわれるプロジェクトでも性能面などの品質が悪かったり、機能拡張が極めて困難だったりと、オブジェクト指向の本来のメリットを享受できません。どうしてこんな非効率なことになっているのかということを、会社の上司などに言うとたいていは、「ハイスキルのスーパープログラマーを確保することは不可能だから」といいます。また、ビジネス的にも少人数のできるプログラマーをわざわざ集めてよいシステムを短期間に作るより、大量の頭数をそろえて非効率に開発した方が(少なくとも短期的な視野からは)儲かります。*8ただし、そのような領域で開発を続ける限りCOBOLVB*9の時代の生産性を大きく超えることはありませんし、長期的には競争力を失って仕事が取れなくなるという危険性すらあるのです。
確かに天才プログラマーを集めて領域③で開発をするということはMicrosoftGoogleのような特殊な会社を除き、一般的な開発プロジェクトでは現実的でないでしょう。ただし、オブジェクト指向などの基礎教育を行い、また、プログラマーの技術力もきちんと評価するようになれば、少なくとも領域②の範囲で仕事をすることは不可能ではないと私は思います。*10そうすればもっと効率的に開発でき、プログラマーも待遇面でもっと評価されるようになり、皆が幸せになれると思いますがいかがでしょうか。
このエントリーをはてなブックマークに追加



*1:COBOLにはCOPY文などテンプレートから一部置換しながら自動的にコピペする仕組みすらある。

*2:Inversion of Control=制御の反転

*3:そうは言ってもPERFORMのくくり出しや段落分割などでいかにきれいに構造化するかといったような工夫の余地は多少なりともありますが。

*4:日本人プログラマーの記憶している英単語の大半が予約語だったりするということです。

*5:本来パターンを導入するにはそれなりの理由=フォースが重要。

*6:ここではIDEなどの開発ツールの差は考慮していません。しかし、重たいアプリケーションサーバーやIDEが本当にCOBOLの開発環境より生産性の向上に寄与しているという証拠もありません。

*7:ここでは多くのSI開発で利用されているJavaフレームワークを例として取り上げましたが、RubyScalaなどの軽量言語やルール記述言語、関数型言語などを考えてみても、プログラミングの柔軟性を奪うという方向の言語はほとんどないと思います。以前よりもプログラマーのセンスやスキルが重要になっているということは一般的な傾向だと言えると思います。

*8:ただし、昨今ではユーザー企業も財布の紐が硬くなっており、生産性や費用対効果を重視することが多く、この従来モデルで売上や利益を上げることが徐々に難しくなってきている傾向があるようです。たまたま会社の合宿で技術系の役員の方と話をする機会があったのですが、多くのSIerで従来の生産性の低いモデルを何とか改善しないといけないという危機感を持っているところが多いと聞きました。ただし、SOAとかクラウドとか新しい言語やツールなどの手段の話は出てきてもプログラマーのスキルの重要性という面には会社のお偉いさん方の考えが及びにくいという傾向はあります。ただし、今後はそういう面に気づいて新しいビジネスモデルで仕事をするSIerが出てきてもおかしくないと思いますし、実際に改革に取り組んでいる会社も多いと信じます。

*9:ここではCOBOLVBを取り上げましたが、PL/SQLTransact-SQLPerlなど一般的にはオブジェクト指向言語より劣るあるいは時代遅れと「信じられている」ような言語に当てはめて考えることもできます。

*10:実際、プログラマーの能力を過小評価する傾向はゼネコン構造も関連した日本のIT業界独自の傾向という話もあります。たとえば、アメリカではSIerに丸投げということは稀であり、ユーザー企業が直接必要なプログラマーを雇うモデルのようなので、生産性やスキルは非常に重視される傾向があります。たとえばSpringのエキスパート的な知識があれば相当高い給料がもらえる一方で、スキルのないプログラマーはすぐに首になるという厳しい面もあるようです。だから、プログラマーも相当がんばって勉強することになります。ある程度そのような生産性が重視される方式に業界の構造が変化していけば、自然と領域①から領域②へ中心がシフトすることにならざるを得ないと思います。

CORBA関連のJava技術について

CORBAで実装されたレガシーなサービス(課金関連?)をJavaのWebアプリケーションと連携させる仕事を担当することになりそうなので、今更ですがCORBA関連のJava技術に関して調査してみました。簡単に結果を以下にまとめます。

CORBAとは

Common Object Request Broker Architecture - Wikipediaによると、さまざまな言語で実装されたオブジェクトを分散化された環境で相互に呼び出すためにOMGによって標準化された規格とのこと。IDL(Interface Definition Language)によって言語非依存のインターフェースを定義しておき、Java、C、C++、Ada、COBOLSmalltalkLISPなどさまざまな言語で実装可能となっています。*1つまり、CORBAでは言語透過性、位置透過性が実現されるということです。さらに、リモート呼び出しに加えて、トランザクション、セキュリティ、メッセージングなどの共通サービスに対する規格もあるようです。現在なら、EJBとかWebサービスなどを使うことが普通だと思いますが、10年くらい前までは結構話題になっていたように記憶しています。

分散オブジェクト技術

CORBAの技術が注目されていた時代(15年くらい前でJavaの誕生時期とかぶる)は、それ以前からあるRPC(リモートプロシージャーコール)に対して分散オブジェクト指向という側面が強調されていたようです。単にリモートのステートレスな手続きをサービスとして呼び出すというのではなく、オブジェクト指向プログラミングの発想を拡張してネットワークごしでオブジェクト同士が通信し合うというというのがCORBAで思い描かれる理想形だったと思われます。ただし、実際には

  • プログラミングモデルの複雑さ
  • ネットワーク通信のオーバーヘッド

などの理由により、純粋な分散オブジェクト指向という考え方はほぼ完全に時代遅れになっていると思います。よってCORBAを使うといっても単純に多言語でステートレスなRPCという使い方をしているケースが大半ではないでしょうか。ただし、インターフェースを決めておけば言語によらずにサービスを実装できるということから、信頼できる枯れたCORBAの実装が利用できるのであれば、必ずしもWebサービスなどに置き換えるよりもよいというケースも一応存在すると思います。

標準プロトコルとしてのIIOP

ORBの実装によって本来の通信プロトコルは異なっているが、相互運用のためにGIOP(General InterORB Protocol)という抽象的なプロトコルの規約が決められ、されにそれをTCP/IP上にマッピングしたものがIIOP(Internet InterORB Protocol)とのことです。理論上は、IIOPを通して複数のベンダー間で相互に通信が可能ということになります。なお、ファイアーウォールのポートを開けばWebサービスと同じようにグローバルなネットワーク上で通信可能なはずですが、実際はHTTP以外通さないことが普通で、場合によってはHTTPでラップ(トンネリング)する必要があります。

JavaとCORBAとの関係

JavaプラットフォームにおけるCORBAサポートについては、基本的には以下のドキュメントに記述されています。
CORBA と Java 技術
EJBWebサービスなどの分散技術が一般化する前の時代ではJavaで分散を行うには

  • ソケットやHTTPなどの低レベルの通信を自分で実装する
  • RMIを使う(JDK1.1から)
  • Java IDLを使ってCORBAを使う(JDK1.2から)

のいずれかの手段を使う必要がありました。JavaのWrite Once Run Anywhereという特性と型づけの強いオブジェクト指向言語という特性からCORBAとの相性が良いと注目されていたようです。ちなみに、当時の本を見るとJavaアプレットからCORBAのサービスと通信するなどのサンプルがよく出てきます。
Javaプログラマーとして、CORBA+Javaのプログラミングの方法を理解する上で、重要なことは、

Java CORBA ORB は RMI プログラミングモデルと IDL プログラミングモデルの両方をサポートしています。

ということですね。2つの異なるプログラミングモデルが規格として存在するために少しややこしくなっています。

IDLプログラミングモデル

Java IDLという規格はJDK1.2の当初から存在していました。現在でもJava SE(Java EEではない)の標準APIの中に含まれています。これは、先ほど述べたようにIDLからさまざまな言語へのマッピングのひとつとしてのJava言語へのマッピングです。また、JDK1.3からはPure JavaのIDLコンパイラ(idlj)とORBの実装が付属しています。Java IDLを使えば、別言語で実装されているCORBAのサービスをJavaから呼び出すことができますし、逆にJavaのオブジェクトをCORBAサービスとして公開することができます。
http://java.sun.com/javase/ja/6/docs/ja/technotes/guides/idl/index.html

RMIプログラミングモデル

CORBAとは別に、もともとJavaに存在していたRMIのプログラミング方式でCORBAのサービスを開発できます。RMIで通常使われるJRMPプロトコルに代わり、CORBAとの相互運用可能なIIOPプロトコルで通信させるための規格としてRMI-IIOPというものが定められています。このモデルだとJava言語でRMI準拠のリモートインターフェースを作成しておけば、他言語で呼び出し可能なIDLを自動生成することが可能です。現在のPOJOサービスからWSDL自動生成というのに比べると面倒ですが、Java言語の範囲で透過的にCORBA通信ができるため、Java IDLのモデルよりはるかに簡単です。ちなみに、EJBのリモートインターフェースはRMI-IIOPを使っているため、CORBAのクライアントから呼び出すことが可能です。

両プログラミングモデルの使い分け

RMIプログラミングモデルはより簡単ですが、IDLを自動生成するという流れでありインターフェースに対して制約があります。したがって、レガシーなCORBAのサービスがすでに存在しており、それに対してラッピングを含めて一切手を加えることができないのであれば、採用することはできません。その場合は既存のサービスのIDLを使ってクライアントスタブを生成し、呼び出すような流れ、つまり、IDLモデルを使って呼び出すしかないようです。一方、Javaでサービスを実装し、別言語のCORBAクライアントから呼び出させる場合には通常RMI-IIOPを使うと便利と思われます。(Java EE環境ならEJBを使うべき?)
Java IDL vs. RMI-IIOP (OCMJEA forum at Coderanch)

Spring FrameworkのCORBAサポート

Spring Remotingの一部として、RMI-IIOPを使ってPOJOクラスをCORBAサービスとして公開したり、逆にクライアント側でインジェクションして呼び出すことがサポートされています。そのためには以下のクラスを使います。

  • org.springframework.remoting.rmi.JndiRmiServiceExporter(サービス側)
  • org.springframework.remoting.rmi.JndiRmiProxyFactoryBean(クライアント側)

なお、Java IDLを使ったIDLプログラミングモデルについては、残念ながら標準機能としてはサポートしていないようです。 ただ、
http://people.yandex-team.ru/~stepancheg/spring-corba/
のようなサードパーティーライブラリーが使えるかもしれません。

*1:非標準ながら、RubyPHPVisual Basicなどのサポートも行う実装もあるらしい。