はじめに
私は現在、未経験からエンジニアへの転職を目指して活動しています。その第一歩として、自分のスキルを具体的にアピールできるポートフォリオとしてECサイトを作成しました。
富士ソフトアカデミーでSpringBootを学びながら、バックエンドの設計・実装を進め、最終的にAWS環境へデプロイしました。また、GitHub Actionsを活用してCI/CDパイプラインを構築し、コードの変更が自動的にデプロイされる仕組みを実装しました。これにより、開発の効率化や継続的な改善が容易に行えるようになっています。本記事では、その概要と実装のポイントについて紹介します。
なお、今回作成したECサイトには支払い機能やお問い合わせ機能などが一部未実装になっており、不完全なECサイトではありますが、温かい目で見ていただけますと幸いです。
作成したECサイトのGitHubはこちらです。
作成背景
近年、爬虫類の飼育が趣味として注目を集め、飼育者やこれから飼おうとしている人々のニーズが増加しています。しかし、爬虫類の飼育には専門的な知識やアイテムが必要であり、初心者にとっては情報が分散しているため、適切な商品を選ぶのが難しいという問題があります。
そのため、初心者でも簡単にアクセスできるECサイトを作成することを決意しました。このサイトでは、爬虫類飼育に必要な商品を購入できるだけでなく、飼育初心者が抱える疑問や不安を解消するために、ユーザー同士が意見交換できる掲示板機能も提供します。さらに、荒らしを防止するため、掲示板への投稿にはユーザー登録を必須としました。
作成期間
2024年12月半ばから2025年2月半ばの約2か月間
・要件定義~設計:約2週間
・実装~テスト:約1か月
・AWSへのデプロイ:約2週間
作成内容
主な使用技術
・ SpringBoot
・ AWS(ECS, RDS, S3, ALB)
・ docker
・ GitHub Actions
要件定義
要件定義については、以下のリンクをご参照ください。
Reptalicious ECサイト 要件定義
設計
設計段階として、機能一覧、画面仕様書、ルーティング定義、テーブル定義、ER図、インフラ構成図、テストケース設計を作成しました。
機能
機能一覧
項目 | 概要 | 認可 |
---|---|---|
商品一覧 | 商品のリストを表示。カテゴリーやキーワードで絞り込み検索が可能。 | 全員 |
商品詳細 | 商品の詳細情報を確認するページ。説明文、価格、レビューなどが表示。 | 全員 |
カート機能 | ユーザーが選んだ商品をカートに追加・削除。購入数量の調整も可能。 | 認証済 |
お気に入り機能 | ユーザーが選んだ商品をお気に入りに追加・削除。 | 認証済 |
購入機能 | ユーザーがカート内の商品を購入する処理。 | 認証済 |
注文履歴機能 | ユーザーが注文した商品の履歴を表示する。 | 認証済 |
レビュー機能 | ユーザーが購入した商品に対してレビューを投稿する。 | 認証済 |
掲示板一覧 | 掲示板を表示。全てのスレッドとコメントの閲覧が可能。不適切だと思う投稿の通報が可能。 | 全員 |
掲示板投稿機能 | スレッドおよびコメントを投稿する機能。 | 認証済 |
ユーザー管理機能 | ユーザー情報の確認、削除ができる管理者向けの機能。 | 管理者 |
商品管理機能 | 商品の追加、編集、削除ができる管理者向けの機能。 | 管理者 |
カテゴリ管理機能 | 商品に紐づけるカテゴリの追加、削除ができる管理者向けの機能。 | 管理者 |
在庫管理機能 | 商品の在庫を変更できる管理者向けの機能。 | 管理者 |
注文管理機能 | ユーザーの注文情報の確認ができる管理者向けの機能。 | 管理者 |
掲示板管理機能 | 掲示板の表示状態の変更ができる管理者向けの機能。 | 管理者 |
画面仕様書
ルーティング定義
認証
メソッド | パス | コントローラメソッド | 説明 | 認可 |
---|---|---|---|---|
GET | /auth/login | showLoginForm() | ログイン画面表示 | 全員 |
POST | /auth/login | - | ログイン処理 | 全員 |
POST | /logout | - | ログアウト処理 | 認証済 |
GET | /auth/register | showRegistrationForm() | 会員登録画面表示 | 全員 |
POST | /auth/register | registerUser() | 会員登録処理 | 全員 |
GET | /auth/complete | registerComplete() | 会員登録完了画面表示 | 全員 |
トップページ
メソッド | パス | コントローラメソッド | 説明 | 認可 |
---|---|---|---|---|
GET | / | showTopPage() | トップページ表示 | 全員 |
GET | /mypage | showMyPage() | マイページ表示 | 認証済 |
GET | /admin | showAdminPage() | 管理者ページ表示 | 管理者 |
会員情報
メソッド | パス | コントローラメソッド | 説明 | 認可 |
---|---|---|---|---|
GET | /users/profile | showProfile() | プロフィール表示 | 認証済 |
GET | /users/profile/edit | editProfile() | プロフィール編集画面 | 認証済 |
POST | /users/profile/edit | updateProfile() | プロフィール更新 | 認証済 |
GET | /users/profile/editPass | showEditPassword() | パスワード変更画面 | 認証済 |
POST | /users/profile/editPass | updatePassword() | パスワード変更 | 認証済 |
注文履歴
メソッド | パス | コントローラメソッド | 説明 | 認可 |
---|---|---|---|---|
GET | /orders | listOrders() | 注文履歴一覧表示 | 認証済 |
GET | /orders/{orderId} | showOrder() | 注文詳細表示 | 認証済 |
お気に入り
メソッド | パス | コントローラメソッド | 説明 | 認可 |
---|---|---|---|---|
GET | /favorites | listFavorites() | お気に入り一覧表示 | 認証済 |
POST | /favorites/add | addToFavorites() | お気に入りに追加 | 認証済 |
POST | /favorites/remove/{favoriteId} | removeFromFavorites() | お気に入りから削除 | 認証済 |
POST | /favorites/toggle | toggleFavorite() | お気に入りの登録・削除 | 認証済 |
カート
メソッド | パス | コントローラメソッド | 説明 | 認可 |
---|---|---|---|---|
GET | /cart | showCart() | カート内容表示 | 認証済 |
POST | /cart/add | addToCart() | カートに商品追加 | 認証済 |
POST | /cart/update/{cartItemId} | updateCartItem() | カート内商品数量変更 | 認証済 |
POST | /cart/remove/{cartItemId} | removeFromCart() | カートから商品削除 | 認証済 |
購入
メソッド | パス | コントローラメソッド | 説明 | 認可 |
---|---|---|---|---|
POST | /purchase/inCart | cartItemsPurchase() | カート内の商品の購入処理 | 認証済 |
商品
メソッド | パス | コントローラメソッド | 説明 | 認可 |
---|---|---|---|---|
GET | /products | listProducts() | 商品一覧表示 | 全員 |
GET | /products/{categoryName} | listProductsByCategory() | カテゴリでの検索結果表示 | 全員 |
GET | /products/keyword | listProductsByNameContaining() | キーワードでの検索結果表示 | 全員 |
GET | /products/{productId} | showProduct() | 商品詳細表示 | 全員 |
レビュー
メソッド | パス | コントローラメソッド | 説明 | 認可 |
---|---|---|---|---|
GET | /reviews/{productId} | listReviews() | レビュー一覧 | 全員 |
GET | /reviews/new | showCreateForm() | レビュー投稿画面 | 認証済 |
POST | /reviews/new | createReview() | レビュー投稿処理 | 認証済 |
掲示板
メソッド | パス | コントローラメソッド | 説明 | 認可 |
---|---|---|---|---|
GET | /bbs | listThreads() | スレッド一覧表示 | 全員 |
GET | /bbs/thread/new | showCreateThreadForm() | スレッド作成画面 | 認証済 |
POST | /bbs/thread/new | createThread() | スレッド作成処理 | 認証済 |
GET | /bbs/{threadId}/edit | showEditThreadForm() | スレッド編集画面 | 認証済 |
POST | /bbs/{threadId}/edit | updateThread() | スレッド更新処理 | 認証済 |
GET | /bbs/{threadId} | showThread() | スレッド詳細表示 | 全員 |
POST | /bbs/{threadId}/remove | removeThread() | スレッド削除 | 認証済 |
GET | /bbs/{threadId}/comment | showCreateCommentForm() | コメント入力画面 | 認証済 |
POST | /bbs/{threadId}/comment | createComment() | コメント投稿処理 | 認証済 |
GET | /bbs/{threadId}/comment/{commentId}/edit | showEditCommentForm() | コメント編集画面 | 認証済 |
POST | /bbs/{threadId}/comment/{commentId}/edit | updateComment() | コメント更新処理 | 認証済 |
POST | /bbs/{threadId}/comment/{commentId}/remove | removeComment() | コメント削除 | 認証済 |
GET | /bbs/reportThread | reportThread() | スレッド通報処理 | 全員 |
POST | /bbs/reportComment | reportComment() | コメント通報処理 | 全員 |
会員(管理者)
メソッド | パス | コントローラメソッド | 説明 | 認可 |
---|---|---|---|---|
GET | /admin/users | listUsers() | 会員一覧画面 | 管理者 |
GET | /admin/users/{userId} | showProfile() | 会員詳細画面 | 管理者 |
POST | /admin/users/{userId}/delete | deleteUser() | 会員削除処理 | 管理者 |
POST | /admin/users/{userId}/restore | restoreUser() | 会員復元処理 | 管理者 |
注文履歴(管理者)
メソッド | パス | コントローラメソッド | 説明 | 認可 |
---|---|---|---|---|
GET | /admin/orders | listAllOrders() | 注文管理画面 | 管理者 |
GET | /admin/orders/{orderId} | showOrderDetail() | 注文詳細表示 | 管理者 |
商品(管理者)
メソッド | パス | コントローラメソッド | 説明 | 認可 |
---|---|---|---|---|
GET | /admin/products | listProducts() | 商品一覧画面 | 管理者 |
GET | /admin/products/create | showCreateForm() | 商品登録画面 | 管理者 |
POST | /admin/products/create | createProduct() | 商品登録処理 | 管理者 |
GET | /admin/products/{productId}/edit | showEditForm() | 商品編集画面 | 管理者 |
POST | /admin/products/{productId}/edit | updateProduct() | 商品更新処理 | 管理者 |
GET | /admin/products/{productId} | showProduct() | 商品詳細画面 | 管理者 |
POST | /admin/products/{productId}/delete | deleteProduct() | 商品削除処理 | 管理者 |
在庫(管理者)
メソッド | パス | コントローラメソッド | 説明 | 認可 |
---|---|---|---|---|
GET | /admin/stocks | listStocks() | 在庫管理画面 | 管理者 |
GET | /admin/stocks/{productId} | showStock() | 在庫変更画面 | 管理者 |
POST | /admin/stocks/{productId}/edit | updateStock() | 在庫更新処理 | 管理者 |
カテゴリ(管理者)
メソッド | パス | コントローラメソッド | 説明 | 認可 |
---|---|---|---|---|
GET | /admin/categories | listCategories() | カテゴリ一覧画面 | 管理者 |
POST | /admin/categories/add | addCategory() | カテゴリ追加処理 | 管理者 |
POST | /admin/categories/toggle | toggleCategory() | カテゴリ削除 | 管理者 |
掲示板(管理者)
メソッド | パス | コントローラメソッド | 説明 |
---|---|---|---|
GET | /admin/bbs/threads/all | listAllThreads() | スレッド管理画面 |
GET | /admin/bbs/threads/{threadId} | showThread() | スレッド詳細画面 |
POST | /admin/bbs/threads/{threadId}/redisplay | redisplayThread() | スレッド再表示 |
POST | /admin/bbs/threads/{threadId}/remove | removeThread() | スレッド削除 |
GET | /admin/bbs/threads/{threadId}/comments/all | listAllComments() | コメント管理画面 |
GET | /admin/bbs/threads/{threadId}/comments/{commentId} | showComment() | コメント詳細画面 |
POST | /admin/bbs/threads/{threadId}/comments/{commentId}/redisplay | redisplayComment() | コメント再表示 |
POST | /admin/bbs/threads/{threadId}/comments/{commentId}/remove | removeComment() | コメント削除 |
テーブル定義
users(会員)
カラム名 | データ型 | NULL | キー | 初期値 | 説明 |
---|---|---|---|---|---|
id | BIGINT | NO | PK | AUTO_INCREMENT | ユーザーID |
VARCHAR | NO | UQ | メールアドレス | ||
password | VARCHAR | NO | パスワード(ハッシュ化) | ||
name | VARCHAR | NO | 氏名 | ||
nickname | VARCHAR | YES | ニックネーム | ||
phone_number | VARCHAR | NO | UQ | 電話番号(ハイフンなし) | |
address | VARCHAR | NO | 住所 | ||
role | VARCHAR | NO | 'ROLE_USER' | 権限('ROLE_USER', 'ROLE_ADMIN') | |
logicalDeleteStatus | VARCHAR | NO | 'ACTIVE' | 論理削除('ACTIVE', 'DELETED') | |
created_at | TIMESTAMP | NO | CURRENT_TIMESTAMP | 登録日時 | |
updated_at | TIMESTAMP | NO | CURRENT_TIMESTAMP | 更新日時 |
products(商品)
カラム名 | データ型 | NULL | キー | 初期値 | 説明 |
---|---|---|---|---|---|
id | BIGINT | NO | PK | AUTO_INCREMENT | 商品ID |
name | VARCHAR | NO | 商品名 | ||
description | TEXT | NO | 商品説明 | ||
price | INT | NO | 価格 | ||
stock | INT | NO | 在庫数 | ||
image_url1 | VARCHAR | YES | 商品画像URL1 | ||
image_url2 | VARCHAR | YES | 商品画像URL2 | ||
image_url3 | VARCHAR | YES | 商品画像URL3 | ||
logicalDeleteStatus | VARCHAR | NO | 'ACTIVE' | 論理削除 ('ACTIVE', 'DELETED') | |
created_at | TIMESTAMP | NO | CURRENT_TIMESTAMP | 登録日時 | |
updated_at | TIMESTAMP | NO | CURRENT_TIMESTAMP | 更新日時 |
categories(カテゴリ)
カラム名 | データ型 | NULL | キー | 初期値 | 説明 |
---|---|---|---|---|---|
id | BIGINT | NO | PK | AUTO_INCREMENT | カテゴリID |
name | VARCHAR | NO | カテゴリ名 | ||
logicalDeleteStatus | VARCHAR | NO | 'ACTIVE' | 論理削除 ('ACTIVE', 'DELETED') | |
created_at | TIMESTAMP | NO | CURRENT_TIMESTAMP | 登録日時 |
product_category(商品-カテゴリ)
カラム名 | データ型 | NULL | キー | 初期値 | 説明 |
---|---|---|---|---|---|
id | BIGINT | NO | PK | AUTO_INCREMENT | 商品カテゴリID |
product_id | BIGINT | NO | FK | 商品ID | |
category_id | BIGINT | NO | FK | カテゴリID | |
created_at | TIMESTAMP | NO | CURRENT_TIMESTAMP | 登録日時 |
orders(注文)
カラム名 | データ型 | NULL | キー | 初期値 | 説明 |
---|---|---|---|---|---|
id | BIGINT | NO | PK | AUTO_INCREMENT | 注文ID |
user_id | BIGINT | NO | FK | ユーザーID | |
total_price | INT | NO | 合計金額 | ||
orderNumber | VARCHAR | NO | 注文番号 | ||
shippingFee | INT | NO | 送料 | ||
created_at | TIMESTAMP | NO | CURRENT_TIMESTAMP | 登録日時 |
orderDetails(注文詳細)
カラム名 | データ型 | NULL | キー | 初期値 | 説明 |
---|---|---|---|---|---|
id | BIGINT | NO | PK | AUTO_INCREMENT | 注文詳細ID |
order_id | BIGINT | NO | FK | 注文ID | |
product_id | BIGINT | NO | FK | 商品ID | |
price | INT | NO | 単価 | ||
quantity | INT | NO | 数量 | ||
created_at | TIMESTAMP | NO | CURRENT_TIMESTAMP | 登録日時 |
cart_items(カート商品)
カラム名 | データ型 | NULL | キー | 初期値 | 説明 |
---|---|---|---|---|---|
id | BIGINT | NO | PK | AUTO_INCREMENT | カートID |
user_id | BIGINT | NO | FK | ユーザーID | |
product_id | BIGINT | NO | FK | 商品ID | |
quantity | INT | NO | 1 | 数量 | |
created_at | TIMESTAMP | NO | CURRENT_TIMESTAMP | 登録日時 | |
updated_at | TIMESTAMP | NO | CURRENT_TIMESTAMP | 更新日時 |
favorites(お気に入り)
カラム名 | データ型 | NULL | キー | 初期値 | 説明 |
---|---|---|---|---|---|
id | BIGINT | NO | PK | AUTO_INCREMENT | お気に入りID |
user_id | BIGINT | NO | FK | ユーザーID | |
product_id | BIGINT | NO | FK | 商品ID | |
created_at | TIMESTAMP | NO | CURRENT_TIMESTAMP | 登録日時 |
reviews(商品レビュー)
カラム名 | データ型 | NULL | キー | 初期値 | 説明 |
---|---|---|---|---|---|
id | BIGINT | NO | PK | AUTO_INCREMENT | レビューID |
user_id | BIGINT | NO | FK | ユーザーID | |
product_id | BIGINT | NO | FK | 商品ID | |
orderDetail_id | BIGINT | NO | FK | 注文詳細ID | |
rating | INT | NO | 1 | 評価値(0~5) | |
description | TEXT | YES | レビュー内容 | ||
created_at | TIMESTAMP | NO | CURRENT_TIMESTAMP | 登録日時 |
threads(スレッド)
カラム名 | データ型 | NULL | キー | 初期値 | 説明 |
---|---|---|---|---|---|
id | BIGINT | NO | PK | AUTO_INCREMENT | スレッドID |
user_id | BIGINT | NO | FK | ユーザーID | |
report | INT | NO | 0 | 通報回数 | |
title | VARCHAR | NO | スレッドタイトル | ||
description | TEXT | NO | スレッド内容 | ||
display | VARCHAR | NO | 'DISPLAY' | 表示状態 ('DISPLAY', 'HIDDEN', 'DELETED') | |
created_at | TIMESTAMP | NO | CURRENT_TIMESTAMP | 登録日時 | |
updated_at | TIMESTAMP | NO | CURRENT_TIMESTAMP | 更新日時 |
comments(コメント)
カラム名 | データ型 | NULL | キー | 初期値 | 説明 |
---|---|---|---|---|---|
id | BIGINT | NO | PK | AUTO_INCREMENT | コメントID |
user_id | BIGINT | NO | FK | ユーザーID | |
thread_id | BIGINT | NO | FK | スレッドID | |
report | INT | NO | 0 | 通報回数 | |
comment | TEXT | NO | コメント | ||
display | VARCHAR | NO | 'DISPLAY' | 表示状態 ('DISPLAY', 'HIDDEN', 'DELETED') | |
created_at | TIMESTAMP | NO | CURRENT_TIMESTAMP | 登録日時 | |
updated_at | TIMESTAMP | NO | CURRENT_TIMESTAMP | 更新日時 |
E-R図
インフラ構成図
インフラ環境は CloudFormation を使用して構築しました。これにより、インフラの構成をコードとして管理できるため、再現性や変更管理が容易になり、インフラの自動構築が可能になっています。
アプリケーションのアーキテクチャとしては、画像のストレージにS3、データベースに RDS(MySQL)を使用し、Webアプリケーションの実行環境として ECS(Fargate)を採用しました。ECSを利用した理由は、プロジェクトをDockerでコンテナ化し、デプロイを簡単にするためです。さらに、Fargateを選択したことで、サーバーの管理が不要になり、スケーラビリティや運用の負担を軽減できるというメリットもあります。
テストケース設計
単体テストおよびE2Eテストのテストケース設計を作成しました。
テストケースの数が多いため、例としてE2Eテストケース設計を示します。
E2Eテスト
項目 | 期待される結果 |
---|---|
ユーザー登録が成功すること | 登録完了のメッセージが表示される |
正しい情報でログインできること | ログインが成功する |
存在しないアカウントでログインできないこと | エラーメッセージが表示されてログインに失敗する |
ユーザーがユーザー情報を変更できること | プロフィールやパスワードが変更される |
キーワードで商品検索が機能すること | キーワードに沿った検索結果が表示される |
カテゴリ検索で商品検索が機能すること | カテゴリに沿った検索結果が表示される |
カートに商品を追加できること | カートに商品が追加される |
お気に入りに商品を追加できること | お気に入りに商品が追加される |
購入手続きが完了すること | 購入完了画面が表示される |
注文履歴が正しく表示されること | 注文履歴一覧が表示される |
ユーザーが商品のレビューを投稿できること | 商品レビューが投稿される |
ユーザーが掲示板に投稿できること | 掲示板にスレッドまたはコメントが投稿される |
管理者が商品を登録できること | 商品一覧に追加される |
管理者がカテゴリを登録できること | カテゴリ一覧に追加される |
管理者が商品の在庫を変更できること | 商品の在庫が変更される |
管理者が掲示板の表示状態を変更できること | スレッドまたはコメントの表示状態が変更される |
使用イメージ
作成したECサイトの使用イメージをいくつか示します。
Login
商品の購入
掲示板への書き込み
商品の登録
工夫した点
掲示板の投稿に対する検閲
検閲機能を実装していないため、代わりに通報ボタンを作成し、通報回数が一定回数を超えた場合、投稿が非表示になる仕様にしました。
削除処理の方法
削除処理を論理削除で行うようにしました。
これにより、データの整合性が保たれ、削除されたデータに対してのデータ分析が可能となります。
AWS環境のIaC化
AWS環境をIaC化し、簡単に環境構築ができるようにしました。
商品画像のリサイズ処理
商品画像をS3にアップロードする際、縦横比を維持したままリサイズして保存しました。
CI/CDの導入
GitHub Actionsを用いてCI/CDを導入することで、開発の効率化と継続的な改善が容易に行えるようにしました。
つまづいた点
CSSの理解不足
CSSの記載に慣れてなく、思い通りに要素を配置するのに苦労した。
テーブル設計の甘さ
設計段階でのテーブル定義が不十分だったため、実装途中でテーブルを追加する必要が生じた。
CloudFormationの知識不足
IaCについて勉強中ということもあり、CloudFormationでの環境構築がうまくいかず、何度も試行錯誤を繰り返した。
コードレビュー環境の不足
コードレビューをしてもらえる環境がなく、書いたコードが正解か分からず、手探りで実装を進めた。
課題
レスポンシブ対応が未実装
画面がレスポンシブ対応していないため、モバイルデバイスや画面サイズによって表示が崩れてしまう。
掲示板の不適切な内容の検閲
掲示板の投稿に対して、不適切な内容の検閲が行われていないため、問題のある投稿がそのまま表示されてしまう。
HTTPプロトコルの利用
HTTPSではなくHTTPプロトコルを利用しているため、セキュリティ面での懸念がある。
今後の展望
各種機能の向上および追加
お問い合わせ機能の追加やコメントに対して返信を紐づけられる機能を実装する。
掲示板投稿に対する検閲システムの導入
掲示板投稿に対してAIを利用した検閲システムを導入し、不適切な投稿の自動判定を実現する。
テストの充実
JUnitによる単体テストおよびE2Eテストを追加し、アプリケーションの動作を保証する。
ECサイトのモダン化
Spring Bootで構築したECサイトのバックエンドをREST API化し、Reactでモダンなフロントエンドを実装する。
セキュリティの強化
ドメインを取得し、SSL証明書を発行することで、HTTPSプロトコルを導入し、セキュリティ面の強化を図る。
まとめ
まだまだ学習中の身であり、完成度には課題が残るものの、自分なりに試行錯誤を重ねながらポートフォリオを作成しました。十分な出来とは言えませんが、これまで学んできたことを形にすることができたと感じています。今後も継続して学習を重ね、より良いものへとブラッシュアップしていきたいと考えています。
今回は、要件定義からデプロイまで自分で一貫して行ったため、自由に設計・実装することができました。しかし、実際の開発ではステークホルダーとの要件のすり合わせが必要になるため、より広い視点での知識が求められます。今回の経験を通じて、ECサイトの開発だけでなく、ECサイトにおけるビジネスモデルの理解など、幅広い知識を身につける必要があると改めて感じました。