SeamFramework.orgCommunity Documentation

第8章 ページフローとビジネスプロセス

8.1. Seamのページフロー
8.1.1. 二つのナビゲーションモデル
8.1.2. Seam と 戻るボタン
8.2. jPDL ページフローの使用
8.2.1. ページフローの設定
8.2.2. ページフローの開始
8.2.3. ページノードと遷移
8.2.4. フローの制御
8.2.5. フローの終了
8.2.6. ページフローコンポジション
8.3. Seam のビジネスプロセス管理
8.4. jPDL ビジネスプロセス定義の使用
8.4.1. プロセス定義の設定
8.4.2. アクターIDの初期化
8.4.3. ビジネスプロセスの初期化
8.4.4. タスク割り当て
8.4.5. タスクリスト
8.4.6. タスクの実行

JBoss jBPM はJava SE や EE 環境のためのビジネスプロセス管理エンジンです。 jBPM はビジネスプロセスやユーザーインタラクションを、 待ち状態、デシジョン、タスク、WEBページなどノードの図式として表現を可能にします。 図式は簡単でとても読みやすい jPDL と呼ばれる XML 表現を使用して定義されており、 Eclipse プラグインを利用して編集、グラフィックによる視覚化が可能です。 jPDL は拡張可能な言語でありWEB アプリケーションのページフローを定義することから、典型的なワークフローの管理、SOA 環境におけるサービスのオーケストレーションまで対応します。

Seam アプリケーションは jBPM を2 つの異なる問題に使用します。

この二つを混乱させないでください!まったく異なるレベルまたは粒度で動作します。Pageflow, conversationtask すべて、一人のユーザーの一つのインタラクションを指しています。ビジネスプロセスはたくさんのタスクに広がります。さらに二つのjBPMアプリケーションは完全に直行しています。それらを一緒にも利用できますし、個別にも利用できます。まったく使わないということもできます。

Seamを使うためにJPDLのことは知らなくていいです。もしページフローの定義やSeamナビゲーションルールの定義に何の問題もなく、アプリケーションがプロセスドリブンよりかはデータドリブンである場合には、おそらくjBPMは必要ないでしょう。しかし、私たちはユーザー間のインタラクションを、よくまとまったグラフィカルな図で考えることは、より堅牢なアプリケーションを構築しやすくしてくれます。

Seam にはページフローを定義する 2 つの方法があります。

簡単なアプリケーションではステートレスなナビゲーションモデルで十分です。 とても複雑なアプリケーションは場所に応じて両方を使用します。 それぞれのモデルはそれぞれの強みも弱みもあります。

ステートレスなモデルは 一組の名前の付いた論理的なイベントの結果 (outcome) から 直接、結果として生じるビューのマッピングを定義します。 ナビゲーション規則は、どのページがイベントのソースであったかということ以外、 アプリケーションによって保持されたどのような状態も全く気にしません。 これは、アクションリスナーメソッドがページフローを決めなければならないことがあることを意味しています。 なぜなら、アクションリスナーメソッドだけがアプリケーションの現在の状態にアクセスできるからです。

これは JSF ナビゲーション規則を使用したページフローの例です。


<navigation-rule>
    <from-view-id
>/numberGuess.jsp</from-view-id>
        
    <navigation-case>
        <from-outcome
>guess</from-outcome>
        <to-view-id
>/numberGuess.jsp</to-view-id>
        <redirect/>
    </navigation-case>

    <navigation-case>
        <from-outcome
>win</from-outcome>
        <to-view-id
>/win.jsp</to-view-id>
        <redirect/>
    </navigation-case>
        
    <navigation-case>
        <from-outcome
>lose</from-outcome>
        <to-view-id
>/lose.jsp</to-view-id>
        <redirect/>
    </navigation-case>

</navigation-rule
>

これは Seam ナビゲーション規則を使用したページフローの例です。


<page view-id="/numberGuess.jsp">
        
    <navigation>
        <rule if-outcome="guess">
            <redirect view-id="/numberGuess.jsp"/>
        </rule>
        <rule if-outcome="win">
            <redirect view-id="/win.jsp"/>
        </rule>
        <rule if-outcome="lose">
            <redirect view-id="/lose.jsp"/>
        </rule>
    </navigation>

</page
>

ナビゲーション規則が冗長過ぎると考えるならば、 アクションリスナーメソッドから直接、ビューIDを返すことが可能です。

public String guess() {

    if (guess==randomNumber) return "/win.jsp";
    if (++guessCount==maxGuesses) return "/lose.jsp";
    return null;
}

これは、リダイレクトの結果であることに留意ください。 リダイレクト中に使用するパラメータを指定することも可能です。

public String search() {

    return "/searchResults.jsp?searchPattern=#{searchAction.searchPattern}";
}

ステートフルなモデルは名前の付いた論理的なアプリケーションの状態間で起こる遷移の組み合わせを定義します。 このモデルではjPDL ページフロー定義中に、どのようなユーザーインタラクションのフロー表現も可能であり、 インタラクションのフローを全く知らないアクションリスナーメソッドを書くことも可能です。

これは jPDL を使用したページフロー定義の例です。


<pageflow-definition name="numberGuess">
    
   <start-page name="displayGuess" view-id="/numberGuess.jsp">
      <redirect/>
      <transition name="guess" to="evaluateGuess">
              <action expression="#{numberGuess.guess}" />
      </transition>
   </start-page>
   
   <decision name="evaluateGuess" expression="#{numberGuess.correctGuess}">
      <transition name="true" to="win"/>
      <transition name="false" to="evaluateRemainingGuesses"/>
   </decision>
   
   <decision name="evaluateRemainingGuesses" expression="#{numberGuess.lastGuess}">
      <transition name="true" to="lose"/>
      <transition name="false" to="displayGuess"/>
   </decision>
   
   <page name="win" view-id="/win.jsp">
      <redirect/>
      <end-conversation />
   </page>
   
   <page name="lose" view-id="/lose.jsp">
      <redirect/>
      <end-conversation />
   </page>
   
</pageflow-definition
>

ここですぐに気づく 二つのことがあります。

それに加えてステートフルモデルはもっと 制約的 です。 各論理的な状態 (ページフローの各ステップ) に対して他の状態に遷移可能な制約された組み合わせがあります。 ステートレスモデルはアドホックな モデルです。 それはアプリケーションではなく、 比較的制約のない、ユーザーが次に行きたいところを決めるフリーフォームナビゲーションに適しています。

ステートフル / ステートレスナビゲーションの違いは、 典型的なモーダル / モーダレスインタラクションの考え方ととてもよく似ています。 さて、アプリケーションをモーダルな振る舞いから回避することは、 対話を持つ 1 つの主な理由ですが、 Seam アプリケーションは、 通常、単純な意味でのモーダルではありません。 しかし、Seam アプリケーションは、 特定な対話レベルで、モーダル可能であり、しばしばそうです。 モーダルな振る舞いは、 可能な限り回避すべきものとして知られています。 ユーザーがしたいことの順番を予測することは、とても困難です。 しかし、ステートフルモデルの存在意義があるのは疑う余地はありません。

二つのモデルの最大の違いは、 戻るボタンの振る舞いです。

JSF あるいは Seam ナビゲーション規則が使用される場合、 Seam は、ユーザーに戻る、進む、更新ボタンの自由なナビゲーションを可能にします。 これが発生したとき、 内部的な対話状態の一貫性を保持することは、 アプリケーションの責任です。 Struts や WebWork のような対話モデルをサポートしない WEB アプリケーションフレームワーク、 そして、EJB ステートレスセッションBean や Spring framework のようなステートレスコンポーネントモデルの組み合わせの経験は、 多くの開発者にこれをすることは、ほとんど不可能であることを教えていました。 しかし、Seam のコンテキストでの経験から、 ステートフルセッション Bean に裏付けられた明確な対話モデルがあるところでは、 それは実際とても簡単です。 通常、それは、アクションリスナーメソッドの最初に、 no-conversation-view-id アノテーションと null チェックの使用を組合わせるのと同じ位に簡単です。 私たちは、フリーフォームナビゲーションのサポートは、 ほぼいつも要求されるものと考えています。

この場合、no-conversation-view-idの宣言は pages.xmlで行います。 対話中のレンダリングされたページからの要求の場合、 それは Seam にリダイレクトを指示し、対話はもはや存在しないことを知らせます。


<page view-id="/checkout.xhtml" 
        no-conversation-view-id="/main.xhtml"/>

一方ステートフルなモデルでは、戻るボタンは以前の状態への未定義な遷移として解釈されます。ステートフルなモデルでは現在の状態から定義済みな遷移のみを行なえるようになっているため、戻るボタンはデフォルトでは有効になっていません。Seam は戻るボタンの利用を透過的に検知します、そして陳腐化した以前のページからの動作を一切ブロックし、単純に現在のページにリダイレクトします。(faces messageを表示します)。これを機能とみるかステートフルモデルの制約とみるかは視点によります:アプリケーション開発者の視点からは機能とみて、ユーザーからの視点としてはフラストレーションを引き起こすようなものかもしれません。back="enabled"を指定することによって特定のページでの戻るボタンを有効にすることもできます。


<page name="checkout" 
        view-id="/checkout.xhtml" 
        back="enabled">
    <redirect/>
    <transition to="checkout"/>
    <transition name="complete" to="complete"/>
</page
>

これは、checkout 状態 から以前のどの状態 にでも戻るボタンでの遷移が可能です。

もちろん、ページフロー時のレンダリングされたページからの要求の場合に何をするのか、そして、その際にページフローでの対話は存在していないのか、といったことを 定義しなければなりません。この場合、no-conversation-view-id の宣言は、ページフロー定義で行います:


<page name="checkout" 
        view-id="/checkout.xhtml" 
        back="enabled" 
        no-conversation-view-id="/main.xhtml">
    <redirect/>
    <transition to="checkout"/>
    <transition name="complete" to="complete"/>
</page
>

実際には両ナビゲーションモデルとも使い道がありますが、どんなときにどちらのモデルの方が適切かを理解するために、これから簡単に学んでいきます。

@Begin@BeginTask あるいは、 @StartTask アノテーションを使用して、 プロセス定義の名前を指定することによって、 jPDL ベースのページフローを開始します:

@Begin(pageflow="numberguess")

public void begin() { ... }

もしくは、pages.xmlを使用してページフローを開始できます。


<page>
        <begin-conversation pageflow="numberguess"/>
    </page
>

If we are beginning the pageflow during the RENDER_RESPONSE phase—during a @Factory or @Create method, for example—we consider ourselves to be already at the page being rendered, and use a <start-page> node as the first node in the pageflow, as in the example above.

しかし、ページフローがアクションリスナー呼び出しの結果として開始される場合、 アクションリスナーの結果 (outcome) は、レンダリングされる最初のページを決定します。 この場合、ページフローの最初のノードとして <start-state> を使用し、 それぞれの可能な結果 (outcome) のために遷移を宣言します。


<pageflow-definition name="viewEditDocument">

    <start-state name="start">
        <transition name="documentFound" to="displayDocument"/>
        <transition name="documentNotFound" to="notFound"/>
    </start-state>
    
    <page name="displayDocument" view-id="/document.jsp">
        <transition name="edit" to="editDocument"/>
        <transition name="done" to="main"/>
    </page>
    
    ...
    
    <page name="notFound" view-id="/404.jsp">
        <end-conversation/>
    </page>
    
</pageflow-definition
>

<page> ノードは、システムがユーザー入力を待っている状態を表します。


<page name="displayGuess" view-id="/numberGuess.jsp">
    <redirect/>
    <transition name="guess" to="evaluateGuess">
        <action expression="#{numberGuess.guess}" />
    </transition>
</page
>

view-id はJSFのビューIDです。 <redirect/>要素はJSFナビゲーションルールにある<redirect/>と同じで、ブラウザの再表示ボタンの問題に対応するためのpost-then-redirectといった振る舞いと同じ効果をもたらします。(Seamはこれらブラウザのリダイレクトをこえて対話コンテキストを渡していきますのでRuby on Rails の "flash"のようなものはSeamでは必要はありません)

遷移名は、numberGuess.jsp において、 ボタン あるいは、リンクをクリックすることによって起動された JSF 結果 (outcome) の名前です。


<h:commandButton type="submit" value="Guess" action="guess"/>

遷移が、このボタンをクリックすることによって起動されるときに、 numberGuess コンポーネントの guess () メソッドと呼び出すことによって、 jBPM は、遷移のアクションを起動します。 jPDL においてアクションを指定するために使わるシンタックスは、 JSF EL 式とよく似ていること、 そして、遷移のアクションハンドラは、 ちょうど現在の Seam コンテキストにおける Seam コンポーネントのメソッドであることに注意してください。 従って、JSF イベントのために既に持っているものと、ちょうど同じ jBPM イベントのモデルを持ちます。 (一貫した原則 (The One Kind of Stuff principle))

nullでのoutcome の場合 (例えば、action が定義されていないコマンドボタン)、 もし、名前のない遷移があるならば、Seam は遷移するためのシグナルを送ります。 あるいは、もし、すべての遷移が名前を持つならば、単純にページを再表示します。 従って、サンプルのページフローとボタンは少し単純化できます。


<h:commandButton type="submit" value="Guess"/>

これは以下の名前のない遷移でのアクションを実行します。


<page name="displayGuess" view-id="/numberGuess.jsp">
    <redirect/>
    <transition to="evaluateGuess">
        <action expression="#{numberGuess.guess}" />
    </transition>
</page
>

ボタンにアクションメソッドを呼ばせることも可能です。 この場合アクション結果 (outcome) が遷移を決定します。


<h:commandButton type="submit" value="Guess" action="#{numberGuess.guess}"/>

<page name="displayGuess" view-id="/numberGuess.jsp">
    <transition name="correctGuess" to="win"/>
    <transition name="incorrectGuess" to="evaluateGuess"/>
</page
>

しかし、これは質の悪いスタイルだと考えます。 なぜならフロー制御の責任をページフロー定義の外側の他のコンポーネントに移動しているからです。 ページフローに関連することを局所化することは、より良いことです。

ビジネスプロセスは誰が、いつ実行されるべきかが明確に定義されたルールの上で、ユーザーやソフトウェアによって実行される明確に定義されたタスクの集合です。 Seam の jBPM インテグレーションはタスク一覧をユーザーに表示することやタスク管理を容易にします。また Seam はアプリケーションにビジネスプロセスに関する状態をビジネスプロセスコンテキストに保管することを可能にします。

<page> の代わりに<task-node> ノードを持つ以外、 簡単なビジネスプロセス定義はページフロー定義とほぼ同じであるように見えます。 (一貫した原則 (The One Kind of Stuff principle)) 長期間のビジネスプロセスにおいて、 待ち状態はシステムが、ユーザーがログインしタスクを実行するのを待っているところです。


<process-definition name="todo">
   
   <start-state name="start">
      <transition to="todo"/>
   </start-state>
   
   <task-node name="todo">
      <task name="todo" description="#{todoList.description}">
         <assignment actor-id="#{actor.id}"/>
      </task>
      <transition to="done"/>
   </task-node>
   
   <end-state name="done"/>
   
</process-definition
>

同じプロジェクトの中にjPDL ビジネスプロセス定義と jPDL ページフロー定義を持つことは可能です。 そうであれば2 つの関係は ビジネスプロセス中の <task>は ページフロー <process-definition>全体と一致します。

いくつかの組み込み Seam コンポーネントによりタスクリストの表示が容易になっています。 pooledTaskInstanceList は ユーザーが自分自身に割り当てることができるプールされたタスクのリストです。


<h:dataTable value="#{pooledTaskInstanceList}" var="task">
    <h:column>
        <f:facet name="header"
>Description</f:facet>
        <h:outputText value="#{task.description}"/>
    </h:column>
    <h:column>
        <s:link action="#{pooledTask.assignToCurrentActor}" value="Assign" taskInstance="#{task}"/>
    </h:column
>                    
</h:dataTable
>

<s:link> の代わりに普通の JSF <h:commandLink> を使用することもできます。


<h:commandLink action="#{pooledTask.assignToCurrentActor}"
> 
    <f:param name="taskId" value="#{task.id}"/>
</h:commandLink
>

pooledTask コンポーネントは単純にタスクを現在のユーザーに割り当てる組み込みコンポーネントです。

taskInstanceListForType コンポーネントは、 現在のユーザーに割り当てられた特定タイプのタスクを含んでいます。


<h:dataTable value="#{taskInstanceListForType['todo']}" var="task">
    <h:column>
        <f:facet name="header"
>Description</f:facet>
        <h:outputText value="#{task.description}"/>
    </h:column>
    <h:column>
        <s:link action="#{todoList.start}" value="Start Work" taskInstance="#{task}"/>
    </h:column
>                    
</h:dataTable
>