2018.12.10

入門Laravelチュートリアル (2) ToDoアプリケーションの設計


この連載記事では、Laravel を使用した Web アプリケーションの開発方法を紹介します。実際に(お決まりの?)ToDo アプリを開発する手順を通して Web 開発のエッセンスを学んでいただけるように書いていきます。取り扱う Laravel のバージョンは現時点で最新の 5.7 です。

👇 電子書籍版も公開しています。

第2章では、コードを書くまえの準備としてToDoアプリケーションの設計を行います。

Web 開発初学者さんを念頭に説明を書きましたので、開発経験があって Laravel のことだけ知りたいという方は読み流して大丈夫です。また次章コードを書いていくのでその前に、この章の最後に Web アプリケーションフレームワークについての説明をつけました。

どんな設計が必要?

Web アプリケーションを作るにあたっては最低限、以下の4つの設計が必要です。

  • 機能一覧
  • 画面設計
  • URL設計
  • テーブル定義

機能一覧

まずは何がしたいアプリケーションなのか?が分からないと始りませんね。アプリケーションが持つべき機能を箇条書きにするのが機能一覧です。

この段階では画面もありません。機能が不明なのに機能を実現するための画面が存在するのはおかしいですよね。また画面をより使いやすく、美しく…と試行錯誤を繰り返しているとたまに「そもそも何がしたいんだっけ?」となることがあるので、そういう時に立ち戻る場所でもあるでしょう。

画面設計

機能が洗い出せたら次にその機能を実現するための画面を考えます。どの機能とどの機能を同じまたは違う画面にすると使いやすいか?、この情報を表現するために最適な UI パーツ(タブとかモーダルとか)は何か?などを考えていくプロセスですね。

システム的には、ある画面では出入力(何を出力するか、または何を入力するか)とアクション(リンクやボタンなどのインタラクション)が重要になってきます。

URL 設計

画面が出揃えば URL を決めることができます。URL 設計ではリクエストメソッド(GET、POST など)と URL の一意な組み合わせがどのような機能を実現すべきかを記述します。

また URL 設計は開発工数を見積もる際の指針にもなります。Web アプリは基本的にリクエストとレスポンスが一対になるため、想定されるリクエストのぶんだけ何かをレスポンスする処理を書く必要があります。URL の本数=リクエストの種類なので、URL の一覧からある程度の開発規模が見えてくるわけです。

テーブル定義

機能と画面からはデータの構成、つまりテーブル定義を導くことができます。画面で何を出力するのか、何が入力されるのかを踏まえてどのようなテーブルが必要かを設計していきます。

テーブル定義も見積もりの指針になります。テーブルの数やリレーションの複雑さはアプリケーションの複雑さに反映されるからです。

認証機能について

今回のチュートリアルでは、ログイン・会員登録・パスワード再設定は Laravel で用意されている認証機能をそのまま使います。フレームワークのデフォルトに従うため、画面設計、URL 設計、テーブル定義を割愛します。認証機能についての章で詳細を見ていくことにしましょう。

さて、ここから先は今回のチュートリアルで開発する ToDo アプリの設計を考えていきます。

機能一覧

ToDo アプリが持つ機能の箇条書きは以下の通りです。

  • ユーザーはタスク(ToDo)を作成することができる。
  • タスクはタイトル、期限日、状態を持つ。
  • タスクの状態とは、「未着手」「着手中」「完了」の3種類である。
  • ユーザーはタスクを一覧表示することができる。
  • ユーザーはタスクのタイトル、期限日、状態を編集することができる。
  • タスクはフォルダに分類して管理する。
  • そのため、ユーザーはフォルダを作成することができる必要がある。
  • また、ユーザーはフォルダの一覧を表示できる必要がある。
  • ユーザーごとにアカウントを持ち、ログインしたユーザーは自分のフォルダおよびタスクだけを閲覧または編集することができる。
  • ユーザーはパスワードを忘れた場合には再登録することができる。

タスクの削除機能やフォルダの編集機能は必要はないのかな?と思った方もいらっしゃるでしょうか。今回はチュートリアルという目的から無理のない分量にしたいので上記の機能だけを作ることにしますが、本来はユーザーが使う姿をいろいろ想像して洗い出せると良いものができるでしょう。

アプリ中の登場人物(フォルダとかタスクとか)に対して CRUD(Create=作成機能、Read=閲覧機能、Update=編集機能、Delete=削除機能)で考えるとある程度システマチックに機能の漏れを抑えることもできます。

実際のビジネスではそもそも「どのような価値を生みたいか(どんな風にユーザーに喜んでもらってどんな風にお金を稼ぐか)」という目的があるはずなので、そちらも忘れてはいけませんね。

画面設計

続いて画面設計です。画面自体は前回お見せしましたので、ここでは出入力とアクションを洗い出していきます。

(少し長いですが、見たままを書いているだけです。)

タスク一覧ページ

出入力

項目 出入力
フォルダ タイトル 出力
タスク タイトル 出力
タスク 状態 出力
タスク 期限日 出力

アクション

項目 種類 働き
フォルダ作成 リンク フォルダ作成ページに遷移する。
タスク作成 リンク タスク作成ページに遷移する。
タスク編集 リンク タスク編集ページに遷移する。

フォルダ作成ページ

出入力

項目 出入力
フォルダ タイトル 入力

アクション

項目 種類 働き
送信 ボタン フォルダを新規作成し、タスク一覧ページに遷移する。

タスク作成ページ

出入力

項目 出入力
タスク タイトル 入力
タスク 状態 入力
タスク 期限日 入力

アクション

項目 種類 働き
送信 ボタン タスクを新規作成し、タスク一覧ページに遷移する。

タスク編集ページ

出入力

タスク編集ページでは登録済みの情報を画面に出力しておく必要もあります。

項目 出入力
タスク タイトル 出力
タスク 状態 出力
タスク 期限日 出力
タスク タイトル 入力
タスク 状態 入力
タスク 期限日 入力

アクション

項目 種類 働き
送信 ボタン タスクを編集し、タスク一覧ページに遷移する。

ヘッダー

画面をまたいで共通で定義されるパーツもあるでしょう。
今回はヘッダーナビゲーションがすべての画面に共有して配置されます。

出入力

項目 出入力
アプリ名 出力
ユーザー名 出力

アクション

項目 種類 働き
ログアウト リンク ログインしている時のみ表示する。
ログアウト処理を行い、ログイン画面に遷移する。
会員登録 リンク ログインしていない時のみ表示する。
会員登録ページに遷移する。
ログイン リンク ログインしていない時のみ表示する。
ログインページに遷移する。

URL設計

次に URL 設計です。考えるべきポイントは大きく二つ、「URL の形式」と「HTTP メソッド」です。

URL の形式

URL の形式とは、URL をどのような文字列にするかということです。URL は基本的に「こうでないと動作しない」というような決まりはないので、どういう文字列がいいのか迷ってしまうかもしれません。しかし一般的な作り方はありますので、慣れれば結構簡単にというか機械的に作れます。

ポイントは「階層構造」と「名詞/動詞」です。
(少々説明が長くなってしまいましたが、最初はそんなもんかと思っておいてください。何度か実践すると頭に入ってきます。)

階層構造

Web アプリに限らずホームページなどでも、Web サイトは階層構造になっているものです。例えば企業のコーポレートサイトがあったとして、サービス一覧ページの下に個別のサービスの説明ページがあり、会社情報ページの下にアクセスや代表者の挨拶ページがある、というような具合です。そして URL はそのような階層構造を反映します。先ほどの企業ページの例ですと、以下のようになるでしょう。

  • 会社情報 → /company
  • アクセス → /company/access
  • 代表者挨拶 → /company/message

コーポレートサイトやホームページが分かりやすいと思いますので、知っている会社やお店のページの構成と URL を見てみてください。

Web アプリケーションでも同様に階層構造を反映した URL にします。今回でいうと、フォルダの中にタスクがあるでしょう。これが階層構造です。アプリケーションの場合ですとホームページとは異なりメニューの階層構造というよりデータ構造を反映した階層になると思います。

タスク一覧の URL を考えてみましょう。(実際の画面があるかどうかは別として、)まず /folders がすべてのフォルダを表します。その中の特定のフォルダが /folders/{フォルダID} で表されます。{フォルダID} にはフォルダを一意に特定するデータベース上のIDが入ると考えてください。さらにその特定のフォルダの中のタスクを表現すると、/folders/{フォルダID}/tasks となります。

名詞/動詞

次に「名詞/動詞」とは、「何を」「どうする」URL なのか、ということです。

「何が」の部分は上記の階層構造の話で触れた通りで、/folders などです。ただこの考え方だけではフォルダを一覧表示するページの URL と作成するページの URL を区別できませんね。そこで「どうする」という動詞を URL に含めます。

具体的には、一覧表示はそのまま /folders、作成ページは /folders/create とします。今回は作りませんが、フォルダの編集ページであれば /folders/edit、削除ページであれば /folders/delete などという URL になるでしょう。

URL の文字列はこの二つの考え方「階層構造」「名詞/動詞」で作るのが基本です。

HTTP メソッド

URL の形式、つまりどのような文字列を使うかだけでは URL 設計は終わりません。どのような HTTP メソッドを受け付けるかも考える必要があります。GET や POST のことです。

最初はまず GET と POST を使い分けられるようにしましょう。まずそれぞれの意味合いとしては、文字通り GET は情報の取得のために使います。POST は情報の送信と処理の依頼のために使います。つまり画面表示系のリクエストには GET を用い、フォームを送信して画面表示以上の何かをしてもらうリクエストには POST を用いましょう。

今回の例でいうと、フォルダ作成画面を表示するメソッドは GET で、作成処理を行うメソッドは POST です。URL と組み合わせると GET: /folders/create がフォルダ作成画面表示で、POST: /folders/create が作成処理の実行です。

URL 一覧

上記の知識を踏まえてまとめた ToDo アプリの URL 一覧は以下の通りです。

URL メソッド 処理
/folders/{フォルダID}/tasks GET タスク一覧ページを表示する。
/folders/create GET フォルダ作成ページを表示する。
/folders/create POST フォルダ作成処理を実行する。
/folders/{フォルダID}/tasks/create GET タスク作成ページを表示する。
/folders/{フォルダID}/tasks/create POST タスク作成処理を実行する。
/folders/{フォルダID}/tasks/{タスクID}/edit GET タスク編集ページを表示する。
/folders/{フォルダID}/tasks/{タスクID}/edit POST タスク編集処理を実行する。

記事中では一つずつ見ていきませんが、ぜひご自身で説明と照らし合わせて確認してみてください。

テーブル定義

設計の最後はテーブル定義です。アプリケーションが出入力する項目をデータベースに保存します。今回はバッチシステムなどはなく画面だけなので、上段の画面設計で洗い出した出入力項目を見てみましょう。

  • フォルダ タイトル
  • タスク タイトル
  • タスク 状態
  • タスク 期限日

会員登録やログインにあたってはユーザーデータが必要になりますが、こちらは Laravel で用意されている仕組みをそのまま使うのでいったん置いておきます。認証機能の章で詳しく見ていきます。

テーブルを決める

さて上の出入力項目をテーブルに分けていきます。リレーショナルデータベースのテーブル設計というと「正規化」の概念がよく知られているかと思いますが、あれはちょっと理屈っぽくて難しいですね。実際は設計に迷ったときに判断のヒントにかもしれないくらいで、「まず一次正規形、それから二次正規形…」などと律儀にテーブル定義を進める人はいません。身も蓋もない言い方ですが、常識的に合理的にやれば正規化されたテーブルになっていきます。何度かアプリケーションを作ればコツがつかめるでしょう。

まずはアプリケーションの「登場人物」を見極めます。これがテーブルになります。今回で言えば「フォルダ」と「タスク」が登場人物になりそうです。このように、それ自体である程度独立して意味のあるまとまりをテーブルにします。オブジェクト指向で言うクラスに近いかもしれません。

カラム

フォルダテーブルとタスクテーブルを作成するとして、次はそれぞれのテーブルが持つべきカラム(列)を決めます。出入力項目を見たままですね。フォルダテーブルにはタイトルカラムが必要で、タスクテーブルにはタイトルカラム、状態カラム、期限日カラムが必要です。

また、リレーショナルデータベースではデータを行と列のまとまり=表(英語でいうとテーブル)形式で保存します。例えばタスクテーブルの一行が一つのタスクを表します。ほとんどの場合、その一行一行を区別するためのカラムを、画面上で出入力する項目とは別に用意します。このカラムはテーブル中で一意(重複してはいけない)でなくてはいけません。このカラムをプライマリキーと呼びます。今回はこのカラムを ID と名付けます(たいていそのように名付けられます)。

さらに、行の作成日と更新日を追加します。アプリケーションに何か問題が起こった時に解決のヒントにするなど、管理上の都合からあると何かと便利なので、大抵の場合に入れておくカラムです。

ここまでの話をまとめると以下のようになるでしょう。

フォルダテーブル

カラム論理名 カラム物理名
ID id
タイトル title
作成日 created_at
更新日 updated_at

タスクテーブル

カラム論理名 カラム物理名
ID id
フォルダID folder_id
タイトル title
状態 status
期限日 due_date
作成日 created_at
更新日 updated_at

テーブル定義ではカラム名を二種類用意することが多いので、今回もそれに倣っています。つまり、論理名と物理名です。理屈っぽい感じがしますが要は論理名が日本語で物理名が英語です。実際のカラム名は英語ですが、それだけだと意味が分からないこともあるので日本語名も付けるわけです。

リレーションシップ

次にテーブル同士のリレーションシップ(関係性)を定義します。

機能一覧によると、タスクはフォルダごとに管理されるのでした。しかし上に示したテーブル定義のままではフォルダとタスクの結びつきが分かりません。この問題は、タスクデータに「どのフォルダに属しているか」という情報を持たせることで解決します。

つまり、タスクテーブルに「フォルダID」カラムを追加します。そして、その「フォルダID」カラムにはフォルダテーブルの行を区別するための「ID」カラムの値を入れることにします。これにより特定のフォルダ行とタスク行の関係性を定義することができます。

ちなみにフォルダとタスクの関係性は「一対多」と呼びます。フォルダ一つに対してタスクが多数紐づくからです。

カラムの型

最後にカラムの型を考えます。リレーショナルデータベースでは最初にテーブルを定義するタイミングでカラムに入るデータ型を決めなくてはいけません。例えば最初に数値しか入らないと決めたカラムには後から文字列を入れることはできません。

ORACLE、MySQL、PostgreSQL など様々なデータベース製品で使用できる型は基本的に同じです。ただしやはり製品ごとに若干独自仕様があります。今回は PostgreSQL を使用しますのでその前提で型を決めていきます。

フォルダテーブル

カラム論理名 カラム物理名 型の意味
ID id SERIAL 連番(自動採番)
タイトル title VARCHAR(20) 20文字までの文字列
作成日 created_at TIMESTAMP 日付と時刻
更新日 updated_at TIMESTAMP 日付と時刻

タスクテーブル

カラム論理名 カラム物理名 型の意味
ID id SERIAL 連番(自動採番)
フォルダID folder_id INTEGER 数値
タイトル title VARCHAR(100) 100文字までの文字列
状態 status INTEGER 数値
期限日 due_date DATE 日付
作成日 created_at TIMESTAMP 日付と時刻
更新日 updated_at TIMESTAMP 日付と時刻

ポイントをいくつか説明します。

ID

まず ID カラムの SERIAL 型は PostgreSQL の独自型です。この型を定義したカラムにはデータベースが自動で連番(1, 2, 3...)を採番してくれます。次に挿入する行が何番かはデータベースがちゃんと管理しているので開発者側が気にする必要はありません。ID カラムのように、テーブル中でユニークであれば意味のない数値でも構わない場合に使用されます。

フォルダID

フォルダIDカラムの型はフォルダテーブルのIDカラムと同じでなくてはいけませんが、SERIAL 型にしてしまうと任意の値が入れられなくなるので INTEGER にします。SERIAL 型のカラムに入るデータは結局数値だからです。

タイトル

フォルダとタスクのタイトルはそれぞれ VARCHAR(20)VARCHAR(100) にしてみました。20文字、100文字までしか入れられないという制約がつきます。別に何文字でもいいのですが今回はこのように決めました。

データベースにデータを入れる前に、アプリケーション側で許容できる文字数を超えていないか検証(バリデーション)する必要が出てきます。

状態

状態カラムは数値として定義しています。「未着手」「着手中」「完了」のどれかというデータを入れるカラムですが、「1」か「2」か「3」のどれかの数字を入れることにします。それぞれの数字がどの言葉に対応するかはデータベースではなくアプリケーションコード側で決めてしまい、「1」であれば「未着手」、「2」であれば「着手中」、「3」であれば「完了」と画面に表示します。

こういう場合は「未着手」などの言葉をそのまま保存することはしません。理由は大きく二つあって、まず①変更に弱くなるからです。仕様変更でやっぱり「着手前」という言葉に変えよう、となったときに影響するデータ行が多いのは不便です。また、②データにも無駄が生じます。数字だけ入れればいいのであれば1文字分の容量で足ります。

設計は以上です。

Web アプリケーションフレームワーク

Laravel は PHP で書かれた Web アプリケーションフレームワークです。コードを書き始める前に、そもそもフレームワークとは何なのか?何のために使うのか?について説明しておきます。

フレームワークとは

EC サイトでも SNS でもニュースサイトでも求人サイトでも、Web アプリケーションに共通して必要な処理があります。

例えば...

  • リクエスト URL に基づいて特定の処理をよびだす
  • ユーザーの認証を行う
  • 入力値のバリデーションを行う
  • データベースに接続する
  • テンプレートにデータを流し込みHTMLを生成する
  • レスポンスメッセージを作成する

などです。

これらの共通処理をまとめたプログラム群が Web アプリケーションフレームワーク(長いので以下 WAF : Web Application Framework)と呼ばれます。

WAF のメリット

では WAF を使うメリットは何でしょうか?

まず、あるあるな処理がフレームワークによって提供されるため開発スピードが早くなります。単純に自分で書かなくてはいけないコードが減るからです。

また、知識が標準化されればチームワークが捗ります。現場ごとの独自フレームワークで開発されていたらいちいち覚え直さなくてはいけないぶん新参開発者の立ち上がりが遅くなります。開発者を集める立場から言っても、例えば Laravel で開発しているチームなら Laravel の経験者を雇えばある程度の即戦力を見込めるでしょう。

このように WAF を使うことで開発効率を向上させることができます。

WAF のパターン

WAF はプロダクトによってそれぞれ書き方やクラスの呼び名などが少しずつ違いますが、基本となるパターンは似ています。いわゆる MVC モデルと言われるフレームワークはたいてい以下のような処理パターンです。

WAF

  1. アプリケーションが HTTP リクエストを受け取る。
  2. リクエストの内容(URL + HTTP メソッド)から、あらかじめ紐づけしておいたメソッドが特定される。紐づけというのは例えば「GET で /profile がリクエストされた時は ProfileController クラスの show メソッドを呼ぶ」という設定のこと。このリクエストの内容とメソッドの紐づけ設定を「ルーティング」と呼ぶ。
  3. リクエスト URL ごとの処理が実行される前に認証チェックなどの共通処理が実行される。この処理はたいてい「ミドルウェア」と呼ばれる。
  4. ミドルウェアの処理を通過するとルーティングによって設定されていたメソッドが呼ばれる。このメソッドは特定の意味のまとまりごとにクラスにまとめられることが多く、この「ルーティング先のメソッドをまとめたクラス」を「コントローラー」と呼ぶ。
  5. 上記で呼ばれたメソッド内の処理は要件次第だが、データベースを操作する際はたいてい「Object-Relational マッパー(ORM)」が用いられる。
  6. レスポンスを返却する際、コンテンツが HTML であれば HTML 生成のために「テンプレート(エンジン)」が用いられる。

このパターンを習得してしまえば今まで使ったことがなかったフレームワークもある程度スムーズに理解することができます。

登場するクラスが多くよく分からなくなった時は上記の図を見返してください。

🍎 🍎 🍎

この章では、ToDo アプリケーションの設計と Web アプリケーションについての説明を行いました。汎用的な設計の考え方も書いたつもりですので、参考になれば幸いです。

次の章から(やっと!?)、まずはフォルダの一覧表示からコードを書いていきます。

連載記事


<!-- Font Awesome Free 5.0.13 by @fontawesome - https://fontawesome.com --><!-- License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->