Piece Frameworkによるブログアプリケーションの作成

第5回画面遷移設計とフロー定義(2)

前回は、ブログアプリケーションの画面遷移設計を行いました。今回は、設計した画面遷移をフロー定義ファイルに落とし込み、実際にPiece_Unityで動作するアプリケーションを作成します。

Piece_Unity 1.0.0から1.1.0へのアップグレード

先日Piece_Unity 1.1.0がリリースされました。作業を始める前にアップグレードを行いましょう。

Piece_Unityのアップグレード
> pear.bat upgrade piece/piece_unity
...
> pear.bat list -a
INSTALLED PACKAGES, CHANNEL __URI:
==================================
(no packages installed)
INSTALLED PACKAGES, CHANNEL PEAR.PHP.NET:
=========================================
PACKAGE             VERSION STATE
Archive_Tar         1.3.2   stable
Cache_Lite          1.7.2   stable
Console_Getopt      1.2.3   stable
HTML_Template_Flexy 1.2.5   stable
Net_URL             1.0.15  stable
PEAR                1.6.1   stable
Structures_Graph    1.0.2   stable
INSTALLED PACKAGES, CHANNEL PEAR.PIECE-FRAMEWORK.COM:
=====================================================
PACKAGE                                          VERSION STATE
Piece_Examples_Basics                            1.0.0   stable
Piece_Flow                                       1.13.0  stable
Piece_Right                                      1.7.0   stable
Piece_Unity                                      1.1.0   stable
Piece_Unity_Component_Authentication             0.13.0  beta
Piece_Unity_Component_Flexy                      1.0.0   stable
Piece_Unity_Component_NullByteAttackPreventation 1.0.0   stable
Stagehand_FSM                                    1.9.0   stable
INSTALLED PACKAGES, CHANNEL PECL.PHP.NET:
=========================================
(no packages installed)

アップグレード完了後、キャッシュディレクトリ/path/to/pieceblog/web/webapp/cache)以下にあるすべてのキャッシュファイル(cache_XXX)を削除してください。

フロー定義ファイルの作成-エントリー一覧フロー

エントリポイントに着目し画面遷移設計を行った結果、アプリケーション全体のフローは新規エントリー入力フロー、エントリー一覧フロー、エントリー編集フローの3つのフローで構成されることになりました。まずはこれら3つのフローのうち、画面がひとつしか存在しない最もシンプルなフローであるエントリー一覧フローをフロー定義ファイルに落とし込んでみましょう。

エントリー一覧フローのステートチャート図
エントリー一覧フローのステートチャート図

上記のステートチャート図は、下記のように表現することができます。

/path/to/pieceblog/web/webapp/config/flows/Entry/List.yaml
firstState: DisplayList

viewState:

  - name: DisplayList
    view: List

フロー定義ファイルは任意の場所に配置することができますが、今回はサンプルアプリケーションで使われているディレクトリ/path/to/pieceblog/web/webapp/config/flows以下にEntryディレクトリを作成し、そこにList.yamlとして配置することにします。

フロー定義ファイルのフォーマットにはYAML[1]が採用されています。YAMLでは配列とハッシュ、スカラー(文字列、数値、真偽値など)を組み合わせることでデータが表現されます。firstState: DisplayListはハッシュのキーと値を表します。firstStateがキーでDisplayListが値です。

firstState要素は、フローが実行された際にイニシャルステートから遷移する先のステート、ファーストステートを表します。基本的に、ファーストステートにはフローの最初の画面を表すステートが指定されることになります。エントリー一覧フローの場合、最初の画面であるエントリー一覧画面を表すDisplayListステートがファーストステートとなっています。

viewState要素は、HTMLテンプレートやリダイレクション先のURLなどのビューを表すステートをひとつ以上持つ配列です。YAMLの配列要素は-⁠ハイフン)で表されます。エントリー一覧フローの場合、最初の画面であるエントリー一覧画面を表すDisplayListステートが、唯一の配列の要素となっています。viewState配列の要素はハッシュとなっており、name要素にはステートの名称、view要素にはHTMLテンプレートのファイル名やリダイレクション先のURLを記述します。Piece_Unityではビューは抽象化されており、ビューがHTMLテンプレートの場合、そのテンプレートのファイル名から拡張子を取り除いたものを指定します。また、ファイル名のパスを含める必要はありません。

なお、本連載中のステートやイベント、イベントハンドラの名称はPiece_IDEで使われている命名規約に従っています。この命名規約では、ビューステートの名称には必ずDisplayという接頭辞が含まれます。

エントリポイントの作成-エントリー一覧フロー

では、エントリー一覧画面が表示されるかどうか試してみましょう。Apacheを起動しブラウザからhttp://pieceblog/list.phpにアクセスしてみてください。

http://pieceblog/list.phpへのアクセス結果画面
http://pieceblog/list.phpへのアクセス結果画面

おっと、404 Not Foundの画面が表示されてしまいました。とはいっても、まだlist.phpを作成していないのでこの結果は当然です。

では、下記のようなスクリプトをlist.phpとして作成してみましょう。

/path/to/pieceblog/web/htdocs/list.php
<?php
error_reporting(E_ALL);

// ブロック1
require_once 'Piece/Unity.php';
require_once 'Piece/Unity/Error.php';

// ブロック2
Piece_Unity_Error::pushCallback(create_function('$error', 'var_dump($error); return ' . PEAR_ERRORSTACK_DIE . ';'));

// ブロック3
$base = dirname(__FILE__) . '/../webapp';

// ブロック4
ini_set('session.cookie_path', str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'])));
session_save_path("$base/sessions");

// ブロック5
$unity = &new Piece_Unity("$base/config", "$base/cache");

// ブロック6
$unity->setConfiguration('Dispatcher_Continuation', 'flowName', 'EntryList');
$unity->setConfiguration('Renderer_Flexy', 'templateDir', "$base/templates/Entry");
$unity->setConfiguration('Renderer_Flexy', 'compileDir', "$base/compiled-templates/Entry");

// ブロック7
$unity->dispatch();
?>

上記のコードを解説します。

  • ブロック1では、require_onceによって必要なファイルのインクルードを行っています。

  • ブロック2では、簡易的なエラーハンドラとしてvar_dump()を使ったものを設定しています。独自のエラーハンドラを設定することで、Piece Framework内のエラーを例外に変換することもできます。

  • ブロック3では、Webアプリケーションのベースディレクトリを変数化しています。アプリケーションの配置先によって動作しなくなることがないように工夫を施しています。

  • ブロック4では、session.cookie_path設定を上書きし、セッションデータの保存ディレクトリを変更しています。ブロック3と同様に、アプリケーションの配置先によって動作しなくなることがないように工夫を施しています。

  • ブロック5では、Piece_Unity設定ファイルの配置先ディレクトリ及びそのキャッシュの配置先ディレクトリを引数にPiece_Unityクラスをインスタンス化しています。

  • ブロック6では、Piece_Unity::setConfiguration()をコールすることによってエントリポイント固有の設定を行っています。プラグインが持つ各設定のデフォルト値やPiece_Unity設定ファイルの設定はこのように上書きすることができます。このような動的な設定はダイナミックコンフィグレーションと呼ばれています。

  • ブロック7では、Piece_Unity::dispatch()をコールすることによってPiece_Unityの実行環境を稼働させています。

では、もう一度エントリー一覧画面が表示されるかどうか試してみましょう。ブラウザからhttp://pieceblog/list.phpにアクセスしてみてください。

http://pieceblog/list.phpへのアクセス結果画面
http://pieceblog/list.phpへのアクセス結果画面

アクセスは成功しましたが、エラーメッセージらしきものが表示されています。エラーメッセージの内容は、フロー定義にEntryListというフロー名が見つからない、というものです。フロー定義はPiece_Unity設定ファイルに記述されていますので、早速Piece_Unity設定ファイルを編集してみましょう。

Piece_Unity設定ファイルへのフロー定義の追加-エントリー一覧フロー

Piece_Unityの設定ファイルは、piece-unity-config.yamlというYAMLファイルです。Piece_Unityの機能はプラグイン[2]を通して設定を行うことができるようになっており、Piece_Unityの設定はプラグインの設定ということになります。フロー定義の設定はDispatcher_Continuationプラグインの設定ポイント[3]flowDefinitionsによって行うことができます。下記はフロー定義の追加を行った設定ファイルです。強調された行が追加分となっています。

/path/to/pieceblog/web/webapp/config/piece-unity-config.yaml
...
- name: Dispatcher_Continuation
  point:
    - name: actionDirectory
      type: configuration
      value: ../webapp/actions
    - name: cacheDirectory
      type: configuration
      value: ../webapp/cache/flows
    - name: flowDefinitions
      type: configuration
      value:
        - name: RegistrationWithNonExclusiveMode
          file: ../webapp/config/flows/Registration.yaml
          isExclusive: false
        - name: RegistrationWithExclusiveMode
          file: ../webapp/config/flows/Registration.yaml
          isExclusive: true
        - name: RegistrationWithExclusiveModeAndAHAH
          file: ../webapp/config/flows/Registration.yaml
          isExclusive: true
        - name: Authentication
          file: ../webapp/config/flows/Authentication.yaml
          isExclusive: true
        - name: ProtectedResource
          file: ../webapp/config/flows/ProtectedResource.yaml
          isExclusive: false
        - name: EntryList
          file: ../webapp/config/flows/Entry/List.yaml
          isExclusive: false
...

では、もう一度エントリー一覧画面が表示されるかどうか試してみましょう。ブラウザからhttp://pieceblog/list.phpにアクセスしてみてください。

http://pieceblog/list.phpへのアクセス結果画面
http://pieceblog/list.phpへのアクセス結果画面

フロー定義の設定を行ったにもかかわらず、先程と同じエラーメッセージが表示されてしまいました。これは、Piece_Unity設定ファイルに記述されたフロー定義の情報がセッションに保存されていることに起因しています。この状態から先に進むためには、ブラウザからセッション用のクッキーを削除するか、サーバのセッションデータを削除するしかありません。ブラウザのセッション用のクッキーを削除する最も原始的な方法はブラウザの再起動です[4]⁠。

では、ブラウザを再起動しhttp://pieceblog/list.phpにアクセスしてみてください。

http://pieceblog/list.phpへのアクセス結果画面
http://pieceblog/list.phpへのアクセス結果画面

ようやくまともなページが表示されました。しかし、ビューとして設定したList.htmlが存在しないため、表示されているのはレイアウト部分のみとなっています。下記のようにList.htmlを作成し、もう一度ブラウザからhttp://pieceblog/list.phpにアクセスしてみてください。

/path/to/pieceblog/web/webapp/templates/Entry/List.html
<h4 class="date-header">List</h4>
http://pieceblog/list.phpへのアクセス結果画面
http://pieceblog/list.phpへのアクセス結果画面

きちんと指定されたビューであるListのHTMLテンプレートが表示されたことがわかります。これでエントリー一覧フローはひとまず完成です。

新規エントリー入力フロー

続いて、新規エントリー入力フローに取りかかりましょう。

新規エントリー入力フローのステートチャート図
新規エントリー入力フローのステートチャート図
/path/to/pieceblog/web/webapp/config/flows/Entry/New.yaml
firstState: DisplayNew

lastState:
  name: DisplayNewFinish
  view: http://example.org/list.php

viewState:

  - name: DisplayNew
    view: New
    transition:
      - event: DisplayNewConfirmFromDisplayNew
        nextState: DisplayNewConfirm

  - name: DisplayNewConfirm
    view: NewConfirm
    transition:
      - event: DisplayNewFinishFromDisplayNewConfirm
        nextState: DisplayNewFinish
      - event: DisplayNewFromDisplayNewConfirm
        nextState: DisplayNew

このフローは、エントリー一覧フローと異なり複数のビューステートが存在するため、それら複数のビューステートを接続する要素であるtransition要素が使われています。

transition要素は、ステート間の遷移を表現する要素です。この要素の値は、イベント名及びイベント発生時の遷移先のステート名の組をひとつ以上持つ配列です。この配列の要素はハッシュとなっており、event要素にはイベント名、nextState要素には遷移先のステート名を記述します。

lastState要素は、ラストステートとなるビューステートです。この要素の値はハッシュとなっており、name要素にはステートの名称、view要素にはHTMLテンプレートやリダイレクション先のURLを記述します。

このフローは新規エントリー入力完了時にエントリー一覧画面に遷移する仕様のため、lastState要素にはエントリー一覧フローのエントリポイントのURLをビューとして指定しています。なお、URLのドメイン部分は実行環境に応じて自動的に置換されるため、必ずしも実際のドメイン名を入力する必要はありません。

/path/to/pieceblog/web/webapp/templates/Entry/New.html
<h4 class="date-header">New</h4>
<p><a href="{__scriptName}?{__flowExecutionTicketKey}={__flowExecutionTicket}&{__eventNameKey}=DisplayNewConfirmFromDisplayNew">Create</a><p>
/path/to/pieceblog/web/webapp/templates/Entry/NewConfirm.html
<h4 class="date-header">NewConfirm</h4>
<p><a href="{__scriptName}?{__flowExecutionTicketKey}={__flowExecutionTicket}&{__eventNameKey}=DisplayNewFromDisplayNewConfirm">Back</a><p>
<p><a href="{__scriptName}?{__flowExecutionTicketKey}={__flowExecutionTicket}&{__eventNameKey}=DisplayNewFinishFromDisplayNewConfirm">Create</a><p>

HTMLテンプレートには遷移に対応したリンクを記述しています。フロー定義に基づいた動作を行うエントリポイント[5]に対しては、実行中のフローを特定するための情報であるフロー実行チケットとイベントを与える必要があります。{}は今回使用するテンプレートエンジン、HTML_Template_Flexyのテンプレート変数を表します。__で始まる文字列は、Piece_Unityが提供するテンプレート変数を表します。では、エントリー一覧フローの場合と同様に、新規エントリポイントの作成とフロー定義の追加を行ってください。

/path/to/pieceblog/web/htdocs/new.php
<?php
error_reporting(E_ALL);

require_once 'Piece/Unity.php';
require_once 'Piece/Unity/Error.php';

Piece_Unity_Error::pushCallback(create_function('$error', 'var_dump($error); return ' . PEAR_ERRORSTACK_DIE . ';'));

$base = dirname(__FILE__) . '/../webapp';

ini_set('session.cookie_path', str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'])));
session_save_path("$base/sessions");

$unity = &new Piece_Unity("$base/config", "$base/cache");
$unity->setConfiguration('Dispatcher_Continuation', 'flowName', 'EntryNew');
$unity->setConfiguration('Renderer_Flexy', 'templateDir', "$base/templates/Entry");
$unity->setConfiguration('Renderer_Flexy', 'compileDir', "$base/compiled-templates/Entry");
$unity->dispatch();
?>
/path/to/pieceblog/web/webapp/config/piece-unity-config.yaml
...
- name: Dispatcher_Continuation
  point:
    - name: actionDirectory
      type: configuration
      value: ../webapp/actions
    - name: cacheDirectory
      type: configuration
      value: ../webapp/cache/flows
    - name: flowDefinitions
      type: configuration
      value:
        - name: RegistrationWithNonExclusiveMode
          file: ../webapp/config/flows/Registration.yaml
          isExclusive: false
        - name: RegistrationWithExclusiveMode
          file: ../webapp/config/flows/Registration.yaml
          isExclusive: true
        - name: RegistrationWithExclusiveModeAndAHAH
          file: ../webapp/config/flows/Registration.yaml
          isExclusive: true
        - name: Authentication
          file: ../webapp/config/flows/Authentication.yaml
          isExclusive: true
        - name: ProtectedResource
          file: ../webapp/config/flows/ProtectedResource.yaml
          isExclusive: false
        - name: EntryList
          file: ../webapp/config/flows/Entry/List.yaml
          isExclusive: false
        - name: EntryNew
          file: ../webapp/config/flows/Entry/New.yaml
          isExclusive: false
...

上記の準備が完了したら、ブラウザを再起動し、エントリポイントにアクセスしてみましょう。

新規エントリー入力画面
新規エントリー入力画面
新規エントリー入力確認画面
新規エントリー入力確認画面
新規エントリー入力完了画面
新規エントリー入力完了画面

今度は最初から問題なく動作します。新規エントリー入力確認画面からCreateリンクをクリックすると先程作成したエントリー一覧画面が表示されます。これは、ステートチャート図の作成時に追加された新規エントリー入力完了画面、すなわちDisplayNewFinishステートのビューがエントリー一覧フローへのリダイレクションを指示しているからです。

エントリー編集フロー

続いて、最後となるエントリー編集フローに取りかかりましょう。

エントリー編集フローのステートチャート図
新規エントリー入力完了画面
/path/to/pieceblog/web/webapp/config/flows/Entry/Edit.yaml
firstState: DisplayShow

lastState:
  name: DisplayDeleteFinish
  view: http://example.org/list.php

viewState:

  - name: DisplayShow
    view: Show
    transition:
      - event: DisplayEditFromDisplayShow
        nextState: DisplayEdit
      - event: DisplayDeleteConfirmViaDisplayShowFromDisplayShow
        nextState: DisplayDeleteConfirmViaDisplayShow

  - name: DisplayEdit
    view: Edit
    transition:
      - event: DisplayEditConfirmFromDisplayEdit
        nextState: DisplayEditConfirm
      - event: DisplayDeleteConfirmViaDisplayEditFromDisplayEdit
        nextState: DisplayDeleteConfirmViaDisplayEdit

  - name: DisplayEditConfirm
    view: EditConfirm
    transition:
      - event: DisplayShowFromDisplayEditConfirm
        nextState: DisplayShow
      - event: DisplayEditFromDisplayEditConfirm
        nextState: DisplayEdit

  - name: DisplayDeleteConfirmViaDisplayShow
    view: DeleteConfirmViaDisplayShow
    transition:
      - event: DisplayDeleteFinishFromDisplayDeleteConfirmViaDisplayShow
        nextState: DisplayDeleteFinish
      - event: DisplayShowFromDisplayDeleteConfirmViaDisplayShow
        nextState: DisplayShow

  - name: DisplayDeleteConfirmViaDisplayEdit
    view: DeleteConfirmViaDisplayEdit
    transition:
      - event: DisplayDeleteFinishFromDisplayDeleteConfirmViaDisplayEdit
        nextState: DisplayDeleteFinish
      - event: DisplayEditFromDisplayDeleteConfirmViaDisplayEdit
        nextState: DisplayEdit
/path/to/pieceblog/web/webapp/templates/Entry/Show.html
<h4 class="date-header">Show</h4>
<p><a href="{__scriptName}?{__flowExecutionTicketKey}={__flowExecutionTicket}&{__eventNameKey}=DisplayEditFromDisplayShow">Edit</a><p>
<p><a href="{__scriptName}?{__flowExecutionTicketKey}={__flowExecutionTicket}&{__eventNameKey}=DisplayDeleteConfirmViaDisplayShowFromDisplayShow">Delete</a><p>
/path/to/pieceblog/web/webapp/templates/Entry/Edit.html
<h4 class="date-header">Edit</h4>
<p><a href="{__scriptName}?{__flowExecutionTicketKey}={__flowExecutionTicket}&{__eventNameKey}=DisplayEditConfirmFromDisplayEdit">Update</a><p>
<p><a href="{__scriptName}?{__flowExecutionTicketKey}={__flowExecutionTicket}&{__eventNameKey}=DisplayDeleteConfirmViaDisplayEditFromDisplayEdit">Delete</a><p>
/path/to/pieceblog/web/webapp/templates/Entry/EditConfirm.html
<h4 class="date-header">EditConfirm</h4>
<p><a href="{__scriptName}?{__flowExecutionTicketKey}={__flowExecutionTicket}&{__eventNameKey}=DisplayEditFromDisplayEditConfirm">Back</a><p>
<p><a href="{__scriptName}?{__flowExecutionTicketKey}={__flowExecutionTicket}&{__eventNameKey}=DisplayShowFromDisplayEditConfirm">Update</a><p>
/path/to/pieceblog/web/webapp/templates/Entry/DeleteConfirmViaDisplayShow.html
<h4 class="date-header">DeleteConfirmViaDisplayShow</h4>
<p><a href="{__scriptName}?{__flowExecutionTicketKey}={__flowExecutionTicket}&{__eventNameKey}=DisplayShowFromDisplayDeleteConfirmViaDisplayShow">Back</a><p>
<p><a href="{__scriptName}?{__flowExecutionTicketKey}={__flowExecutionTicket}&{__eventNameKey}=DisplayDeleteFinishFromDisplayDeleteConfirmViaDisplayShow">Delete</a><p>
/path/to/pieceblog/web/webapp/templates/Entry/DeleteConfirmViaDisplayEdit.html
<h4 class="date-header">DeleteConfirmViaDisplayEdit</h4>
<p><a href="{__scriptName}?{__flowExecutionTicketKey}={__flowExecutionTicket}&{__eventNameKey}=DisplayEditFromDisplayDeleteConfirmViaDisplayEdit">Back</a><p>
<p><a href="{__scriptName}?{__flowExecutionTicketKey}={__flowExecutionTicket}&{__eventNameKey}=DisplayDeleteFinishFromDisplayDeleteConfirmViaDisplayEdit">Delete</a><p>
/path/to/pieceblog/web/htdocs/edit.php
<?php
error_reporting(E_ALL);

if (file_exists(dirname(__FILE__) . '/../../imports')) {
    set_include_path(dirname(__FILE__) . '/../../imports/spyc-0.2.5' . PATH_SEPARATOR .
                     dirname(__FILE__) . '/../../imports/pear'
                     );
}

require_once 'Piece/Unity.php';
require_once 'Piece/Unity/Error.php';

Piece_Unity_Error::pushCallback(create_function('$error', 'var_dump($error); return ' . PEAR_ERRORSTACK_DIE . ';'));

$base = dirname(__FILE__) . '/../webapp';

ini_set('session.cookie_path', str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'])));
session_save_path("$base/sessions");

$unity = &new Piece_Unity("$base/config", "$base/cache");
$unity->setConfiguration('Dispatcher_Continuation', 'flowName', 'EntryEdit');
$unity->setConfiguration('Renderer_Flexy', 'templateDir', "$base/templates/Entry");
$unity->setConfiguration('Renderer_Flexy', 'compileDir', "$base/compiled-templates/Entry");
$unity->dispatch();
?>
/path/to/pieceblog/web/webapp/config/piece-unity-config.yaml
...
- name: Dispatcher_Continuation
  point:
    - name: actionDirectory
      type: configuration
      value: ../webapp/actions
    - name: cacheDirectory
      type: configuration
      value: ../webapp/cache/flows
    - name: flowDefinitions
      type: configuration
      value:
        - name: RegistrationWithNonExclusiveMode
          file: ../webapp/config/flows/Registration.yaml
          isExclusive: false
        - name: RegistrationWithExclusiveMode
          file: ../webapp/config/flows/Registration.yaml
          isExclusive: true
        - name: RegistrationWithExclusiveModeAndAHAH
          file: ../webapp/config/flows/Registration.yaml
          isExclusive: true
        - name: Authentication
          file: ../webapp/config/flows/Authentication.yaml
          isExclusive: true
        - name: ProtectedResource
          file: ../webapp/config/flows/ProtectedResource.yaml
          isExclusive: false
        - name: EntryList
          file: ../webapp/config/flows/Entry/List.yaml
          isExclusive: false
        - name: EntryNew
          file: ../webapp/config/flows/Entry/New.yaml
          isExclusive: false
        - name: EntryEdit
          file: ../webapp/config/flows/Entry/Edit.yaml
          isExclusive: false
...

上記の準備が完了したら、ブラウザを再起動し、エントリポイントにアクセスしてみましょう。

エントリー参照画面
エントリー参照画面
エントリー編集画面
エントリー編集画面
エントリー編集確認画面
エントリー編集確認画面
エントリー削除確認画面1
エントリー削除確認画面1
エントリー削除確認画面2
エントリー削除確認画面2

今度も最初から問題なく動作します。画面遷移図ではひとつだったエントリー削除確認画面をふたつの異なるステートにしたのは、参照画面を経由したか編集画面を経由したかによって戻り先のステートが異なること、経由したステート以外のステートに戻れることがあってはならないこと、この2点を満たすためです。実際にリンクをコピーし、それぞれの削除確認画面を表示させたあと、そのリンクをブラウザから入力してしてみてください。Piece_UnityのWebフローコントロール機能により、不正なイベントが無効化され単に現在のステートのビューが表示されることがわかるでしょう。

フローの実行の復元にフローの実行毎に発行されるチケットが必要なこと、不正なイベントは無効化され定義された遷移しか行えないこと、これらはPiece_Unityを使用したアプリケーションが、不正リクエストやCSRFといった問題から自動的に保護されることを意味します。

おわりに

これで画面遷移設計とフロー定義は完了です。これまでの作業で、画面遷移のみではありますが、ブログアプリケーションを実行環境で稼働させることができました。

次回は、各画面の詳細な設計を行います。

おすすめ記事

記事・ニュース一覧