目的
WEB開発フローを理解してちょっとしたWEBアプリを作成できる
なぜ
実務の流れが分かる理由
開発設計段階から解説、作成していくので実務の流れを理解するにはうってつけかと思います。
会員登録画面を作成する理由
web開発で言うと最初にやるアプリとしてTodoリストがあります。
しかし、データの流れを余り理解できづらいという点があります。
画面の遷移を通してデータの流れを知った方がwebに関する知識は理解しやすいと感じました
対象読者
- https://qiita.com/5huyA/items/8550db92055808c29173 に書いてある記事の内容が分かる人
目次
- 完成品
- システム開発の設計段階を理解する
- システム開発の設計の実施
- 実際にコードを書いてみる
完成品
git はこちらです。
-
vscodeで開いて
Ctl Shift @
を押下してターミナルを表示してcargo run
で実行してください。
システム開発の設計段階を理解して使いこなす
システム開発の設計段階のそれぞれを理解する
要求定義とは
なにを作るか決めることです。
社長が「城を作ろう!」と言ったとします。でも、どんな城かはまだ決めていません。大きいのか小さいのか、どんな色か、どんな形か。まずは、社長と一緒に「どんな城を作るか」をしっかりと決める必要がありますね。これが「要求定義」のステップです。
要件定義とは
城の詳しい計画をします。お城を作る計画をもっと詳しくします。たとえば、「城には高い塔が4つあって、真ん中に大きな門がある」とか、「城の中にはお姫様の部屋がある」という具体的なことを決めます。これで、「どんな城を作るか」のアイデアが具体的になりました。これが「要件定義」です。
基本設計とは
城の大きなパーツを決めることです。城の大きなパーツをどうするかを考えます。例えば、塔はどれくらいの高さにするか、門はどうやって開け閉めするかなど、お城の大きな部分の設計をします。お城の全体的な形もここで考えます。これが「基本設計」のステップです。
詳細設計
城の小さなパーツまで決めることです。
城の小さな部分まで細かく決めます。窓はどのように作るか、壁にはどんな装飾をするかなど、細かい部分を決めていきます。これで、実際に城を作る時に困らないように、すべての部分が計画されます。
システム開発の設計の実践
実践編です。実際に書いてみましょう。
要求定義
ぽかぽかサイトの会員登録画面を作成したい。
ユーザーには名前とメールアドレスとパスワードを入力してもらいたい。
機能要件
- 入力画面を作成できる
- ユーザーが名前、メールアドレス、パスワードを入力できる
- 確認画面を作成できる
- ユーザーが入力したデータを示し、確認を求めることができる
- 完了画面を作成できる
- ユーザーが入力画面で作成したデータを示すことができる
基本設計
- register.html
- 目的:ユーザーが名前、メールアドレス、パスワードを入力するフォームを提供できる
- 機能:入力フォームには、入力フィールド、データを送信するための「確認画面へ」ボタンが含まれる
- confirm.html
- 目的:ユーザーが入力したデータを確認できる
- 機能:入力されたデータを表示し、完了画面へデータが遷移される
- finish.html
- 目的:データの登録が完了したことをユーザーに通知できる
- 機能:登録完了のメッセージを表示できる
- main.rs
- ユーザーのデータ受け取り、確認画面、完了画面へのデータ受け渡しを行う
詳細設計
ユースケース図
ユースケース図というのは、システムの中がどうなっているかではなくて、外から見たときにシステムがどう動くかを描いた図のことです。これは、システムを使う人、つまりエンドユーザーがどういう風にシステムを使うかを、簡単な言葉で表したものです。
シーケンス図
シーケンス図とは、プログラムの処理の流れや概要についての処理を時間軸に沿って図で表現するものです。
疑似コード
最後に疑似コードで作成していきましょう。
疑似コードとは処理の流れを日本語で書いてみるというものです。
これが出来れば実装は実質出来たも同然です!!頑張りましょう。
- src直下にregister.htmlを作成する
- src直下にconfirm.htmlを作成する
- src直下にfinish.htmlを作成する
- register.htmlに記述する
- head
- 文字化けをしないようにutf-8を追記
- 画面のタイトルを会員登録入力画面とする
- body
- h3サイズで
ぽかぽかサイトへようこそ
- formでデータを送信する(postメソッドでactionは/confirmで紐づけられたmain.rsの処理を呼び出す)
- inputタグで名前を受け付ける(送信するデータの名前はusername。typeはtext)
- inputタグでメールアドレスを受け付ける(送信するデータの名前はmailaddress。typeはtext)
- inputタグでパスワードを受け付ける(送信するデータの名前はpassword。typeはtext)
- h3サイズで
5 confirm.html
- head
- 文字化けをしないようにutf-8を追記
- 画面のタイトルを会員登録確認画面とする
- body
- h3サイズで
ぽかぽかサイトへようこそ
- main.rsから取得されたデータを表示する
- formでデータを送信する(postメソッドでactionは/finishで紐づけられたmain.rsの処理を呼び出す)
- inputタグで名前を受け付ける(送信するデータの名前はusername 。更にデータを渡すためにtypeはhiddenにする)
- inputタグでメールアドレスを受け付ける(送信するデータの名前はmailaddress。更にデータを渡すためにtypeはhiddenにする)
- inputタグでパスワードを受け付ける(送信するデータの名前はpassword。更にデータを渡すためにtypeはhiddenにする)
- h3サイズで
6 finish.html
- head
- 文字化けをしないようにutf-8を追記
- 画面のタイトルを会員登録完了画面とする
- body
- h3で
入力ありがとうございました
- main.rsから取得されたデータを表示する
- h3で
7 main.rs
- Actix-webとserdeを使用してサーバー機能を実装
- HTMLテンプレートエンジンとしてTeraを使用
- GET / : register関数を呼び出し、登録フォームを表示
- POST /confirm : confirm関数を呼び出し、入力データを確認画面に送信
- POST /finish : finish関数を呼び出し、登録を完了し完了画面を表示
- サーバーをlocalhostのポート8080で起動
お疲れ様です。あとは実装するだけですね!!
実際にコードを書いてみる
use actix_web::{App,web,get,post,HttpResponse,HttpServer,Responder};
use serde::{Serialize,Deserialize};
use tera::{Tera, Context};
#[derive(Serialize,Deserialize,Clone)]
struct FormData{
username : String,
mailaddress : String,
password : String
}
#[get("/")]
async fn register(tera : web::Data<Tera>)-> impl Responder{
let context = Context::new();
tera.render("register.html",&context)
.map(|body| HttpResponse::Ok().content_type("text/html").body(body))
.unwrap_or_else(|_| HttpResponse::InternalServerError().finish())
}
#[post("/confirm")]
async fn confirm(tera : web::Data<Tera>,form : web::Form<FormData>)-> impl Responder{
let mut context = Context::new();
context.insert("user",&form.into_inner());
tera.render("confirm.html",&context)
.map(|body| HttpResponse::Ok().content_type("text/html").body(body))
.unwrap_or_else(|_| HttpResponse::InternalServerError().finish())
}
#[post("/finish")]
async fn finish(tera : web::Data<Tera>,form : web::Form<FormData>)-> impl Responder{
let mut context = Context::new();
context.insert("user",&form.into_inner());
tera.render("finish.html",&context)
.map(|body| HttpResponse::Ok().content_type("text/html").body(body))
.unwrap_or_else(|_| HttpResponse::InternalServerError().finish())
}
#[actix_web::main]
async fn main()->std::io::Result<()>{
let tera = Tera::new("src/**").expect("Teraのインスタン生成に失敗しました");
HttpServer::new(move ||{
App::new()
.app_data(web::Data::new(tera.clone()))
.service(register)
.service(confirm)
.service(finish)
})
.bind("127.0.0.1:8080")?
.run()
.await
}
#[derive(Serialize,Deserialize,Clone)]
struct FormData{
username : String,
mailaddress : String,
password : String
}
serdeはデータを簡単に扱うためのツールです。SerializeとDeserializeは、データを保存したり読み込んだりするときに使います。これによって、web上のデータをプログラムが理解できる形にデータを変換したり、その逆をしたりできます。
最後に、teraはテンプレートエンジンと呼ばれるもので、ウェブページの見た目を作るために使います。Teraはテンプレートエンジンの名前で、Contextはウェブページに表示するデータを管理するためのものです。これが無いとmain.rsからconfirm.htmlへデータを渡せないです。
#[derive(Serialize,Deserialize,Clone)]はFormDataという構造体(データをまとめたもの)に特別な機能を追加するためのコードです。SerializeとDeserializeを追加することで、この構造体のデータを簡単に保存したり読み込んだりできるようになります。Cloneは、この構造体のデータをコピーする機能を追加します。
FormData構造体は、ユーザーの情報を保存するために使います。usernameはユーザー名、mailaddressはメールアドレス、passwordはパスワードを表しています。これらの情報はウェブアプリケーションでユーザーがアカウントを作るときに使われるものです。
#[post("/confirm")]
async fn confirm(tera : web::Data,form : web::Form)-> impl Responder{
let mut context = Context::new();
context.insert("user",&form.into_inner());
tera.render("confirm.html",&context)
.map(|body| HttpResponse::Ok().content_type("text/html").body(body))
.unwrap_or_else(|_| HttpResponse::InternalServerError().finish())
}
まず、#[post("/confirm")] という部分は、ウェブサイトの特定のアドレス(ここでは "/confirm" というアドレス)にデータを送るときにこのコードが動くように設定しています。これは「ポストリクエスト」と呼ばれる方法で、例えばウェブサイトのフォームに情報を入力して送信ボタンを押したときに使われます。
async fn confirmは、非同期関数というもので普通の関数は順番に一つずつ作業をするけど、非同期関数は「他の作業が終わるのを待っている間に、こっちの作業もやっておくね」という感じで、同時にいろいろな作業を進めることができる関数です。「confirm」という名前の機能を持っています。
tera : web::Data, form : web::Formは、この関数が使う二つのデータを意味しています。tera はウェブページの見た目を作るためのツールで、form はユーザーがフォームに入力したデータです。
関数の中では、まず Context::new()で新しい「コンテキスト」というものを作ります。コンテキストは、ウェブページに表示する情報をまとめるための箱のようなものです。
次に context.insert("user",&form.into_inner());で、ユーザーがフォームに入力したデータをコンテキストに追加します。こうすることで、そのデータをウェブページに表示できるようになります。
tera.render("confirm.html",&context)は、コンテキストの情報を使って "confirm.html" というウェブページのテンプレートを完成させる命令です。これにより、ユーザーが入力したデータを含むウェブページが作られます。
最後に、mapと unwrap_or_elseは、ウェブページがうまく作れたらそのページをユーザーに見せるためのものです。もし何か問題があってページが作れなかったら、代わりにエラーのメッセージを表示します。
他のメソッドも同様です。
後はhtmlを作っていきましょう!!
<!DOCTYPE html>
<html lang="ja">
<head>
<title>会員登録入力画面</title>
<meta charset="UTF-8">
</head>
<body>
<h3>ぽかぽかサイトへようこそ</h3>
<form action="/confirm" method="post">
<p>お名前 : <input type="text" name="username"></p>
<p>メールアドレス : <input type="text" name="mailaddress"></p>
<p>パスワード : <input type="text" name="password"></p>
<button type="submit">確認画面へ</button>
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="ja">
<head>
<title>会員登録確認画面</title>
<meta charset="UTF-8">
</head>
<body>
<h3>ぽかぽかサイトへようこそ</h3>
<p>お名前は : {{user.username}}</p>
<p>メールアドレス : {{user.mailaddress}}</p>
<p>パスワード : {{user.password}}</p>
<form action="/finish" method="post">
<input type="hidden" name="username" value="{{user.username}}">
<input type="hidden" name="mailaddress" value="{{user.mailaddress}}">
<input type="hidden" name="password" value="{{user.password}}">
<button type="submit">完了画面へ</button>
</form>
</body>
</html>
hiddenにする理由
画面には見えないけれど、情報を保存しておく場所のことです。ユーザーが以前のページで入力したデータを次のページに引き継ぐために利用しています。
<!DOCTYPE html>
<html lang="ja">
<head>
<title>会員登録完了画面</title>
<meta charset="UTF-8">
</head>
<body>
<h3>登録完了しました</h3>
<p>お名前は : {{user.username}}</p>
<p>メールアドレス : {{user.mailaddress}}</p>
<p>パスワード : {{user.password}}</p>
</body>
</html>
実行するにはターミナルを開いていCtl Shift @
でターミナルを開いてcargo run
で実行できます。
127.0.0.1:8080
を開いてみてみましょう。
お疲れ様でした!!!
大変だったと思いますがこれで一連のデータの動きが分かったのではないでしょうか?