@DataModel/@DataModelSelectionを使った一覧選択機能の実装はアンチパターンか?
Seamのステートフルなアーキテクチャーを活かした例として、入門書などにはよく@DataModelと@DataModelSelectionを組み合わせて一覧からエンティティを選択するような例が書かれています。たとえば、Seamのディストリビューションのexamples\messagesというサンプルには以下のようなコードが入っています。
@Stateful @Scope(SESSION) @Name("messageManager") public class MessageManagerBean implements Serializable, MessageManager { @DataModel private List<Message> messageList; @DataModelSelection @Out(required=false) private Message message; @PersistenceContext(type=EXTENDED) private EntityManager em; @Factory("messageList") public void findMessages() { messageList = em.createQuery("select msg from Message msg order by msg.datetime desc").getResultList(); } public void select() { if (message!=null) message.setRead(true); } public void delete() { if (message!=null) { messageList.remove(message); em.remove(message); message=null; } } @Remove @Destroy public void destroy() {} }
よって、Webアプリケーションでよくあるように、一覧画面から特定の行のエンティティを選択して、詳細画面に遷移させるようなことをやろうとした場合、同様の方法で実装しているケースを見受けます。私も最初はそのそのような実装しかやり方がないと思っていたのですが、実は、そうではないということに最近気づきました。むしろこの方法は一般的な一覧⇒詳細のパターンを実装するのには向かないのではないかとすら思えてきました。むしろ、アンチパターンなのではないかとも思います。そう思える理由は以下の通りです。
- DataModelによる行選択はJSFのポストバックが前提のため、ブックマークや別ウィンドウ表示に対応できない
- エンティティのList全体を最低でもページスコープより広いスコープに保持し続けなくてはならないためスケーラブルではない
前者の問題はSeamの
実際にSeamの自動生成機能でエンティティから自動生成を行うと、Seam Application Frameworkに基づいて、XXXHomeというクラスとXXXListというクラスが生成されるのですが、一覧を扱うためのListクラスの方は実はEventスコープとなっています。リスト選択時はURLにIDを埋め込んでGETで送信し、Homeクラスに対して@RequestParameterでIDをインジェクションした後、詳細画面からアクセス時にエンティティをfindしなおす実装となっています。
@Name("personHome") public class PersonHome extends EntityHome<Person> { private static final long serialVersionUID = 1L; @RequestParameter Long personId; @Override public Object getId() { if (personId == null) { return super.getId(); } else { return personId; } } @Override @Begin public void create() { super.create(); } }
<h:dataTable id="personList" var="person" value="#{personList.resultList}" rendered="#{not empty personList.resultList}"> <h:column> <f:facet name="header">Id</f:facet> #{person.id} </h:column> <h:column> <f:facet name="header">Name</f:facet> <s:link id="person" value="#{person.name}" propagation="none" view="/person.xhtml"> <f:param name="personId" value="#{person.id}"/> </s:link> </h:column> </h:dataTable>
Homeクラスは会話スコープを使っていますが、長期会話の開始は詳細画面を開くタイミングとなっています。この方式なら、一覧画面の表示自体を会話スコープに含める必要がなく、永続コンテキスト中に大量のmanagedなエンティティを保持する必要がないため、上記の問題を2つとも解決できています。
上の例だと@RequestParameterを使ったパラメーターのインジェクション機能を利用していますが、もちろん、pages.xmlでページパラメーターを宣言して、IDを受け取ることも可能です。
@DataModelSelectionは、選択対象がenumとか要素数の非常に限られたエンティティの場合で、かつ、画面遷移を伴わない同一画面内でのポストバックの際のみ(ショッピングカートに追加したり選択項目を削除したりするような場合など)に限定して使うのがベストプラクティスなのではないかと思います。