モデルファサードの利用

ステータス:Early Access
Sean Brydon、 Smitha Kangath

課題

Java EE 5 アプリケーションで Java Persistence API を使用し、ドメインモデルを作成することができます。モデル層内のエンティティーにアクセスするコードのデザインでは、サーブレット、JSP ページ、または JSF 管理対象 Bean などの呼び出し元クライアントコードが、エンティティーの API にアクセスするようにします。 Java Persistence エンティティーを利用して、クライアントは、モデル内のエンティティーへのアクセスに必要なトランザクション API、EntityManager API、および持続性 API のその他の機能を内部に実装する必要があるかもしれません。 また、クライアントコードは、いくつかのエンティティーとの対話が必要になることがあります。これらエンティティーはそれぞれ異なる API を持ち、トランザクションやエンティティーマネージャーのオペレーションに必要な条件も異なることがあります。 アプリケーションが大きくなると、コードは、使用するモデルのクライアント用 API を大量に持つことができ、これは、クライアントコードがエンティティーモデルと密接に結合するにつれて管理が難しくなります。

対処法

ファサードパターンは、呼び出し元クライアントと Java Persistence API のモデルエンティティー、およびオペレーション間の対話をカプセル化し、一元化する上位クラスを定義します。ファサードパターンは、一群のエンティティーに対するオペレーションに単一のインタフェースを提供します。このファサードを利用するクライアントは、各エンティティーの API の詳細をすべて意識する必要はなく、エンティティーマネージャーまたはそのほかのトランザクションマネージャーにアクセスする際に使用される、持続性機構を意識する必要もありません。呼び出し元クライアントとモデル層のエンティティーとの間にファサードを導入することによって、コードが疎結合になり、保守が容易になります。

コード例 1 に示すように、ファサードを利用したコードはすっきりとしたものになります。ファサードを利用しないと、エンティティーのクライアントはそのエンティティーの詳細を知っている必要があり、エンティティーへのアクセスが必要になるたびに、類似のコードをほかの場所にカット & ペーストすることになります。ファサードを導入後のコードでは、モデル層のエンティティーの細部の多くが隠されています。

ファサード導入前 ファサード導入後
public class MyWebClient ... {
  
  ...


  public void doGet(HttpServletRequest request,
            HttpServletResponse response)
{
...
//データベースに新規 Item を
//追加するために Web 要求を処理
if (selectedURL.equals("additem.do")) {
  String desc = request.getParameter("item_desc");
  String name = request.getParameter("item_name");
  ...
  Item item = new Item();
  item.setName(name);
  item.setDescription(desc);

//これでモデル層のエンティティーにアクセスします
  EntityManager em =
          emf.createEntityManager();
    try{
      utx.begin();
      em.joinTransaction();
      em.persist(item);
      utx.commit();
    } catch(Exception exe){
            System.out.println("Error persisting item: "+exe);
        try {
            utx.rollback();
        } catch (Exception e) {}
    } finally {
        em.close();
    }
  }
...

public class MyWebClient { ...

...

  public void doGet(HttpServletRequest request,
            HttpServletResponse response)
{
...
//データベースに新規 Item を
//追加するために Web 要求を処理
if (selectedURL.equals("additem.do")) {
  String desc = request.getParameter("item_desc");
  String name = request.getParameter("item_name");
  ...
  Item item = new Item();
  item.setName(name);
  item.setDescription(desc);
  //これでモデル層のエンティティーにアクセスします
  Item item = new Item();
  myfacade.addItem(item);
  }
コード例 1: モデル層のエンティティーにアクセスするためのファサード導入によるクライアント側呼び出しコードのリファクタリング

ファサードのデザイン時に検討する事項は次のとおりです。 使用したファサードでどのような選択がされているのか、その一部を見てみましょう。サンプルアプリケーションでは、Web ファサードとセッション Bean ファサードの両方を示しています。以下に、これらのサンプルをより詳しく取り上げます。

方法 1: Web コンポーネントファサードの使用

ファサードを実装する 1 つの方法として、ファサードクラスとして Web コンポーネントを使用する方法があります。この方法は、Web 専用のアーキテクチャーが必要で EJB コンテナを使用しない場合、あるいは単に Web コンテナにファサードを保持する場合に有用です。アプリケーションが Web 専用のアーキテクチャーを使用し、そのエンティティーにアクセスする Web コンポーネントがある場合、ファサードを使用すると、コードの整理に役立つことがあります。

Web ファサード構築の実装の詳細について述べます。

public class CatalogFacade implements ServletContextListener {

 @PersistenceUnit(unitName="CatalogPu") private EntityManagerFactory emf;
 @Resource UserTransaction utx;

 public CatalogFacade(){}

 public void contextDestroyed(ServletContextEvent sce) {
   if (emf.isOpen()) {
     emf.close();
   }
 }

 public void contextInitialized(ServletContextEvent sce) {
   ServletContext context = sce.getServletContext();
   context.setAttribute("CatalogFacade", this);
 }

 public void addItem(Item item) throws InvalidItemException {
   EntityManager em = emf.createEntityManager();
   if(item.getName().length() == 0)
     throw new InvalidItemException("The item" + " name cannot be empty. Pease specify a name for the item.");

   try {
     utx.begin();
     em.joinTransaction();
     em.persist(item);
     utx.commit();
   } catch(Exception exe){
     System.err.println("Error persisting item: "+exe);
     try {
       utx.rollback();
     } catch (Exception e) {
       System.err.println("Error persisting item: "+ e.getMessage(), e);
     }
     throw new RuntimeException("Error persisting item: "+ exe.getMessage(), exe);
   } finally {
     em.close();
   }
 }

 public Item getItem(int itemID){
   EntityManager em = emf.createEntityManager();
   Item item = em.find(Item.class,itemID);
   em.close();
   return item;
 }

 public List getAllItems(){
   EntityManager em = emf.createEntityManager();
   List items = em.createQuery("SELECT OBJECT(i) FROM Item i").getResultList();
   em.close();
   return items;
 }

}

コード例 2: エンティティー Bean とモデルのファサードとしての Web コンポーネント

コード例 2 での注目点は、ファサードに ServletContextListener インタフェースを実装させていることです。こうすることにより、Java EE プラットフォームの Java Persistence エンティティーや関連サービスにアクセスするときに有用な注釈を利用できます。サーブレットなどの Web コンポーネントがこのファサードを検索して利用する方法を見てみましょう。たとえば addItem メソッドには、エンティティーにアクセスするためのトランザクション境界用の JTA API が必要です。また、データベースに新しい Item を格納するには、EntityManager API が必要です。ファサードクラス内の 1 箇所にこうしたコードをまとめると、エンティティーへのアクセスが必要になるたびにアプリケーションの至るところに類似のコードをカット&ペーストするよりも便利です。

public class CatalogServlet extends HttpServlet {
  private ServletContext context;
  private CatalogFacade cf; 

  public void init(ServletConfig config)throws ServletException {              
context = config.getServletContext();
cf = (CatalogFacade)context.getAttribute("CatalogFacade");
initPathMapping();
  }

  public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {     
  ...       
try {           
if (selectedURL.equals("additem.do")) {          
    //要求から値を取得し、
//その値を新規 Item に設定して、データベースに追加
String desc = request.getParameter("item_desc");              
String name = request.getParameter("item_name");              
...               
Item item = new Item();               
item.setName(name);            
item.setDescription(desc);              
  ...
cf.addItem(item);  //こうしてから、ファサードを呼び出す
  ...           
}
}
コード例 3: モデルファサードを使って Java Persistence エンティティーにアクセスする Web コンポーネント

コード例 3 での注目点は、サーブレットの init メソッドによってファサードを取得し、そのファサードをほかのクライアント要求で利用、共有できるよう、サーブレットにフィールドとして格納していることです。要求を処理する際、サーブレットはファサードを使用することができ、エンティティーへのアクセスに必要なエンティティーや持続性関係の細部に関わる必要がなくなります。

方法 2: セッション Bean ファサードの利用

ファサードを実装するもう 1 つの方法として、セッション Bean をファサードとして使用する方法があります。このデザインでは、セッション Bean をサポートするものとして EJB コンテナを使用する必要があります。この方法のメリットは、コンテナ管理トランザクションなどのコンテナサービスをセッション Bean が利用できることです。このため、JTA API を使用するトランザクションを管理するための余分なコードを含める必要はありません。

@Stateless()
public class CatalogFacadeBean implements CatalogFacade {  

  @PersistenceContext(unitName="CatalogPu")
  private EntityManager em;

  public void addItem(Item item) throws InvalidItemException {
    if(item.getName().length() == 0)
      throw new InvalidItemException("The item" +
        " name cannot be empty. Please specify a name for the item.");
    em.persist(item);
  }

  public Item getItem(int itemID) {
    Item item =  em.find(Item.class,itemID);
    return item;
  }

  public List<Item> getAllItems() {
    List<Item> items = em.createQuery("SELECT OBJECT(i) FROM Item i").getResultList();
    return item;
  }
}

コード例 4: エンティティーおよびモデル層のファサードとしてのセッション Bean

コード例 4 では、トランザクションの境界を設定するコードがないことに注意してください。この例では、EJB コンテナのコンテナ管理トランザクションサービスを利用することにしています。目的のトランザクションの振る舞いは注釈 (ここではデフォルトを使用) で指定することも、また配備記述子内で指定することもできます。この例ではさらに、EntityManager API に対する呼び出しをコードに含めなくてもいいようにコンテナ管理エンティティーマネージャーを使用するようにしています。代わりに、EJB コンテナにその働きをさせています。アプリケーション管理のエンティティーマネージャーとトランザクションを使用するようにした場合は、それらサービスをプログラム的に管理するためのコードが必要になります。

public class CatalogServlet extends HttpServlet {
  private Map nameSpace;
  private ServletContext context;
  @EJB(name="CatalogFacadeBean") private CatalogFacade cf;  

  public void init(ServletConfig config) throws ServletException {
    context = config.getServletContext();       
    initPathMapping();
  }  

  public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    ...       
    try {
    if (selectedURL.equals("additem.do")) {
      //要求から値を取得し、新規 Item にその値を設定
      String desc = request.getParameter("item_desc");
      String name = request.getParameter("item_name");
      ...
        Item item = new Item();
      item.setName(name);
      item.setDescription(desc);
      ...
      //ファサードを使用して新規 Item をデータベースに追加
      cf.addItem(item);
      ...         
    }
    ...
  }

コード例 5: セッション Bean ファサードを使って Java Persistence エンティティーにアクセスする Web コンポーネント

コード例 5 では、サーブレットは、@EJB(name="CatalogFacadeBean") を使ってファサードをルックアップし、取得するために、依存性注入を使用しています。ほかのクライアント要求で利用、共有できるよう、ファサードをフィールドとして格納しています。ファサードを利用することで、モデル層へのアクセスの詳細が隠されます。

参考資料

次に参考資料を挙げます。

c Sun Microsystems 2006. Java BluePrints Solutions Catalog の内容はすべて著作権保護されており、サン・マイクロシステムズ社の書面による許可なしに他の著作物に発表することを禁止します。