はじめに
この記事では、バックエンドエンジニアを目指す実務未経験者がポートフォリオとして作成した Web アプリケーションを紹介します。
自身の振り返りも兼ねて、アプリケーションの概要や使用した技術についてまとめましたので、同じような境遇の方々にとって、少しでも参考になる点があれば嬉しく思います。
アプリケーションの概要
アプリケーション名:「 お薬日記 」
「お薬日記」は、服用したお薬の記録をサポートする Web アプリケーションです。
主な使用技術は、Spring Boot (Kotlin) / Vue.js (JavaScript) / AWS ECS (Fargate) です。
現在、このアプリのサーバーは停止していますが、全てのソースコードは GitHub で公開しています。
機能一覧
お薬日記では、以下のような機能を提供しています。
■ メイン機能
-
服用記録追加機能
- 服用記録の一覧表示 (薬・日付・ユーザーなどによるフィルタリング)
- 服用記録のカレンダー表示
-
薬情報登録機能
- 薬の画像のトリミングとアップロード
- 在庫情報の設定 (服用記録追加に伴う在庫数の自動更新)
- 薬の一覧表示 (症状による検索)
-
共有機能
- ユーザー検索
- 共有グループへの招待
- 薬情報並びに服用記録の公開・非公開設定
■ その他
-
認証機能
- ソーシャルログイン (初回ログイン時にアカウント自動作成)
-
プロフィール設定機能
- プロフィール画像のトリミングとアップロード
-
お問い合わせ機能
- お問い合わせ内容の確認メール送信
■ 画面イメージ
ホーム画面 | カレンダー画面 | 設定画面 |
---|---|---|
薬一覧画面 | 薬詳細画面 | 服用記録モーダル |
---|---|---|
■ 操作イメージ
閲覧 | 服用記録追加 | 共有グループ招待 |
---|---|---|
共有機能が有効になっている場合、UI は情報の見やすさと操作性を重視したデザインに切り替わります。これにより、どのユーザーの情報かが一目でわかるため、スムーズに共有を行うことができます。
共有していない場合 | 共有している場合 |
---|---|
作成背景
方向性とアプローチ
アプリケーションの開発にあたり、主に以下の 3 点を重視しました。
1. 個人利用に特化したアプリケーション
構想初期の段階では、多くのポートフォリオ紹介記事で見られる SNS 系アプリケーションの開発を考えていました。
しかし、SNS 系アプリケーションはユーザー同士のコミュニケーションが主要な特徴であり、サービスとして成立させるには集客が大きな課題となります。また、プライバシーやセキュリティ、不適切なコンテンツの取り締まりなど、設計段階から運用に至るまで多くの懸念事項があります。
今回は「現在のスキルでできる限り価値のあるサービスを提供しつつ、ユーザーの安全性は最優先したい」という思いから、個人利用または限られたコミュニティでの利用に特化したアプリケーションの開発を選択しました。
2. 自身をターゲットユーザーとする
アプリケーションの要件を決定する際は、私自身の経験と需要を出発点としました。
このアプローチには以下のメリットがあります。
1. 実際のユーザーニーズを深く理解できる
2. 具体的な要件を明確に設定できる
3. 開発の優先順位づけが容易になる
自身の経験に基づいて機能を設計することで、実用的で直感的なアプリケーションの開発を目指しました。ただし、個人の視点のみに偏らないよう、類似アプリケーションの調査を行い、周囲の意見も積極的に取り入れることを心掛けました。
3. 継続的に改善を続ける
このアプリケーションは、自身のスキルアップに合わせて新しい機能や技術も導入していく予定です。また、積極的にユーザーのフィードバックを取り入れて、改善していきたいと考えています。
これらを実現するために、効率的なコード管理やドキュメント管理、CI/CD などに注力し、保守性と拡張性の確保を目指しました。
コンセプト決め
私は長年にわたり、薬を手放せない生活を送ってきました。様々な治療を行う中で、医師から「症状の変化や薬の服用状況とその効果を記録すると治療に役立つ」とのアドバイスを受けました。実際に記録を続けてみると、次第に症状のパターンやトリガーが見えてきて、治療方法を適切に調整できるようになりました。
しかし、記録はできる限り詳細に残す必要があるため、1 日に何度も書き込むとなると手書きでは手間がかかりました。また、ページが増えるにつれて必要な情報を探すのが難しくなり、振り返る際にも苦労していました。
メモ帳アプリや日記アプリも試しましたが、そのような記録に特化していないため、入力の手軽さや閲覧のしやすさに欠け、長くは続けられませんでした。
そこで、これらの問題を解決するために、薬の服用を簡単に記録し、ストレスなく振り返ることができる専用のアプリケーションを開発することにしました。
ただ、この機能だけでは魅力に欠け、利用シーンも限定されてしまうので、以下のような機能も実装しました。
■ 薬の詳細情報を登録する
用法や用量、飲み合わせなど、薬に関する情報は非常に多岐にわたります。インターネットで検索するとしても、メーカーや成分の微妙な違いがあるため、必要な情報を見つけるのに時間がかかってしまうことがあります。また、効果や副作用には個人差があるため、そうした情報は自身で管理するしかありません。
このような問題を解決するために、本アプリでは薬の詳細情報を一元管理する機能を実装しました。
■ 在庫情報を管理する
風邪薬や痛み止めなどの常備薬は、家族間で共有することが多いかと思います。しかし、必要なときに薬がなかったり、気づかないうちに有効期限が切れていたりすることは、多くのご家庭で経験する問題ではないでしょうか。
この問題を解決するために、薬の残量や有効期限をいつでもどこでも確認できる在庫情報管理機能を追加しました。
■ 薬情報や服用記録を家族やパートナーと共有する
健康管理は個人の問題だけでなく、家族やパートナーとの協力が重要です。しかし、体調が悪くても「大したことではない」と思って伝えなかったり、相手に負担をかけまいとして言い出せないこともあるでしょう。お互いの健康状態を適切に共有し、コミュニケーションを取るというのは、なかなかに難しい問題です。
このような状況を改善するため、家族やパートナーと薬情報や服用記録を簡単に共有できる機能を実装しました。
認証機能について
本アプリケーションでは、パスワード認証を採用せず、ソーシャルログインのみを提供しています。この選択は、セキュリティリスクを軽減しつつ、ユーザー体験を向上させることを目的としています。
一般的に、多くのユーザーは複数のサービスで同じパスワードを使いまわす傾向にあります。そのため、1 つのサービスでパスワードが漏洩すると、他のサービスにまで影響が及ぶ可能性が高いです。本アプリケーションでは、このリスクを回避するためにパスワード認証を導入しませんでした。
一方で、ソーシャルログインには以下のようなメリットがあります。
-
パスワード管理の負担を軽減
アプリケーション内でユーザーのパスワードを保管する必要がなくなり、セキュアな情報の管理にかかるリスクと負担を軽減できます。 -
信頼性の高いプラットフォームのセキュリティを活用
大手プラットフォームの堅固なセキュリティ対策を活用することで、アプリケーションの安全性が向上します。 -
ユーザーにとっての利便性向上
数クリックで簡単にログインできるため、ユーザーにとっての利便性が大幅に向上し、ストレスのないログイン体験を提供します。
これらの理由から、ソーシャルログインはセキュリティと利便性のバランスが取れた認証方法であると判断し、採用に至りました。
使用した技術
バックエンドから学習を始めて、開発の楽しさに目覚めたという経緯もあり、バックエンドの設計と実装には特に力を入れました。
一方で、フロントエンドとインフラについては、ほとんど知識がない状態で開発に着手したため、「まずは動作させること」を目標に技術選択を行いました。
バックエンド
- Kotlin 1.8.2
- Spring Boot 3.1.2
- Spring Security (spring-boot-starter-security) 3.0.6
- Spring Boot Actuator (spring-boot-starter-actuator) 3.1.0
- Flyway 9.21.0
- MyBatis (mybatis-spring-boot-starter) 3.0.0
- Thymeleaf (spring-boot-starter-thymeleaf) 3.0.6
- JUnit (AssertJ) 3.23.1
- SpringMockK 4.0.2
- Gradle 7.6.1
言語:Kotlin
はじめは、プログラミングに興味を持ったきっかけであり、慣れ親しんでいた Java での開発を考えていました。しかし、新しい言語を学び、技術的な視野を広げたいという思いから、最終的に Kotlin を採用することに決めました。
Kotlin を選択した主な理由は以下の通りです。
-
Java との高い互換性
Kotlin は Java と高い互換性があるため、既存の Java 知識を活かしつつ、新しい機能をスムーズに学ぶことができる。 -
関数型プログラミングの要素
Kotlin は関数型プログラミングの要素をより強力にサポートしており、より自然に実装できるように設計されている。 -
高いセキュリティと生産性
Null 安全機能やスマートキャスト、不変性の推奨など、セキュリティを強化しつつ生産性を高める機能が充実しており、安心してコードを書くことができる。
フレームワーク:Spring Boot
以下のような理由から、フレームワークには Spring Boot を採用しました。
- Java / Kotlin ベースの Web アプリケーション開発におけるデファクトスタンダードである
- コンテナ化やクラウドデプロイメントとの相性が良好
- ドメイン駆動設計の実装サポートがある
- 豊富な機能と拡張性が提供されている
さらに、セキュリティを強化し、脆弱性を軽減するために、Spring Security も併せて導入しました。
Spring Security は、認証・認可機能に加えて CSRF や XSS などの攻撃からアプリケーションを包括的に保護するセキュリティ機能を提供しているため、高度なセキュリティ機能を効果的に導入することができます。
O/R マッパー:MyBatis
O/R マッパーには、MyBatis を採用しました。
MyBatis は SQL クエリとオブジェクトを直接マッピングする SQL マッパーです。
SQL の学習を深めたいという私の目的に合致しており、また、公式ドキュメントが充実していることや Spring Boot Starter が提供されているため、導入や設定が明瞭であることも決め手となりました。
マイグレーションツール:Flyway
マイグレーションツールには、Flyway を採用しました。
Flyway はデータベーススキーマの変更を効率的に追跡し、管理するためのツールです。
主な選定理由としては、Spring Boot との高い親和性が挙げられます。
Flyway は Spring Boot に組み込まれると、アプリケーション起動時に自動的にマイグレーションを実行し、データベーススキーマを更新します。これにより、アプリケーションとデータベースの整合性が常に保たれるため、開発者はスキーマの同期について心配する必要がありません。
特に、AWS ECS (Fargate) + RDS といった本番環境へのデプロイ時もこの自動マイグレーション機能が働きます。アプリケーションの起動と同時にデータベースの更新が行われるため、手作業による更新作業が不要となり、デプロイプロセス全体の信頼性が向上します。
設計手法
ドメイン駆動設計
バックエンドでは、ドメイン駆動設計 (DDD) を導入しました。
DDD は、複雑なドメインを扱うソフトウェア開発において、ビジネスの概念や規則を中心に設計を行うアプローチです。
本アプリケーションは小規模で複雑な要件を含みません。また、私自身がプログラミング初心者であることを考慮すると、DDD の完全な適用はオーバーエンジニアリングになる可能性があります。
しかし、以下のような理由から戦術的パターンの概念を中心に適用することにしました。
- ソフトウェア設計の重要な原則や考え方を理解する
- 学んだ知識を実際のプロジェクトに適用することで、より実践的なスキルを身につける
- コードの構造化やドメインモデルの表現方法を改善し、品質を高める
DDD の基本概念を部分的に取り入れることで、ドメインモデルの表現力を高めつつ、現在の開発規模に適したバランスのとれた設計を目指しました。
ICONIX プロセス
具体的なモデリング手法としては、ICONIX プロセスを導入しました。
ICONIX プロセスはユースケースを中心に据えたモデリング手法で、要件定義から設計、実装、テストまでの一連のステップをサポートします。
DDD 自体はモデリングに関する具体的な手順を規定していないため、ICONIX プロセスを補完的に使用しました。
今回は振る舞いの明確化と詳細な要件定義、DDD との連携を目的として、以下の 3 つに焦点を当てて実施しました。
- ドメインモデリング
- ユースケースモデリング
- ロバストネス分析
また、本来 ICONIX プロセスは多くのドキュメント作成を伴いますが、開発効率と保守性を考慮して、一部の図を省略またはカスタマイズして作成しました。
モデリング成果物
■ ドメインモデル図
■ 画面遷移図
■ E-R 図
※ ロバストネス図やエンドポイントに関する図については、サイズが大きくなるため割愛しました。よろしければ、別途 GitHub をご参照ください。
オニオンアーキテクチャ
アーキテクチャには、オニオンアーキテクチャを採用しました。
オニオンアーキテクチャは、外側から内側への依存関係を持つレイヤー化された設計を提供する、ドメイン駆動設計の原則と相性が良いアーキテクチャです。
採用した主な理由としては、以下の 3 点あります。
- ドメインモデルがアーキテクチャの中心に位置するため、DDD の概念を自然に適用できる
- 各層の責務が明確に分離されるため、コードの組織化と保守が容易
- 内側の層は外側の層に依存しないため、ドメインロジックを技術的な実装の詳細から保護できる
オニオンアーキテクチャと DDD の原則に基づいて、以下のようなパッケージ構造を採用しました。
├── application
│ ├── query (クエリサービス: 読み取り専用のデータ取得ロジックを提供する)
│ └── service (アプリケーションサービス: ユースケースを実装し、ドメインモデルを調整・連携する)
├── domain
│ └── model (ドメインモデル: エンティティ、値オブジェクト、ドメインサービス、およびリポジトリインターフェースを含む)
├── infrastructure
| ├── db (データベース実装: リポジトリインターフェースの実装やデータベースへのアクセスを管理する)
| ├── email (メール実装: メール送信に関する実装を行う)
| └── objectstorage (オブジェクトストレージ実装: オブジェクトストレージサービスへのアクセスを管理する)
└── presentation
└── controller
├── api (API コントローラ: API エンドポイントを提供し、リクエストの処理とレスポンスの生成を行う)
└── page (Page コントローラ: HTML ページのレンダリングとユーザーインタラクションを処理する)
各層の役割は以下の通りです。
レイヤー | 役割 |
---|---|
domain | ビジネスロジックとドメインモデルを含む中心層 |
application | ドメイン層のオーケストレーションとユースケースの実装 |
infrastructure | 外部依存(データベース、メール、オブジェクトストレージなど)の実装 |
presentation | ユーザーインターフェースの責務を担う最外層 |
この構造により、各層の責務を明確に分離し、特にドメイン層を外部の依存から保護しています。さらに、DDD の戦術的パターンを各層に適切に配置することで、アーキテクチャと DDD の原則を効果的な統合を実現しています。
テスト戦略
バックエンドでは、以下のような各層の特性に応じたテスト戦略を採用し、テストスイート全体の保守コストを削減しつつ、システムの正常性を確保しています。
レイヤー | テスト観点 |
---|---|
domain / infrastructure | 複雑な処理や重要な処理に焦点を当てた選択的なテスト |
application | 個々の要求が適切に実装されていることを確認 |
presentation | エンドポイントの動作とアクセス制御を検証 |
application 層のテストでは、ロバストネス図から導出したテストケースを使用しています。infrastructure 層と domain 層のテストは、application 層のテストカバレッジを考慮し、必要に応じて実施しています。
また、テストの信頼性と効率性のバランスを取るために、以下の方針を採用しました。
1. データベースを使用するテスト: 実際のデータベースとやり取りし、モックは使用しない
2. 外部サービスを使用するテスト: モックを使用する
この組み合わせにより、システムの中核となるデータ操作の信頼性を確保しつつ、外部要因による不安定性を排除しました。
なお、バックエンドとフロントエンドを統合した E2E テストについては、UI の頻繁な変更に対応するために、現時点では手動テストを採用しています。
例外戦略
本アプリケーションの例外処理は、Spring Boot のトランザクション管理機能を基盤として、DDD の原則に基づいた実装を行いました。
具体的には以下のアプローチを採用しています。
-
ドメイン固有の例外定義
ドメインロジックに関連する例外を、独自の非検査例外クラスとして定義しています。これにより、ドメインの概念をより明確に表現し、トランザクション内で例外が発生した場合は自動的にロールバックが行われます。 -
集中的な例外処理
例外処理の効率化と統一化を図るため、個別の例外ハンドリングやログ出力は最小限に抑え、共通の例外ハンドラクラスを実装しています。 -
ユーザーフレンドリーなエラー通知
共通の例外ハンドラクラスでは、各種例外をキャッチして適切なエラーログを出力するとともに、状況に応じて以下の処理を行います。- エラーページへのリダイレクト
- API 利用時には適切な HTTP ステータスコードで構造化された JSON レスポンスの返却
この実装により、ドメインの概念を明確に表現しつつ、一貫性のある例外処理と利用者にわかりやすいエラー通知を実現しました。
バックエンドとフロントエンドの統合
本アプリケーションは、シンプルで小規模な構造を持ち、限られたトラフィック量を扱っています。この状況に適した設計として、バックエンドとフロントエンドを統合した構成を採用しました。
具体的には、Node.js を使用したフロントエンドを、バックエンド(Gradle プロジェクト)のサブプロジェクトとして統合し、一つのプロジェクトにまとめています。そして、静的リソース(HTML、CSS、JavaScript、画像ファイル)の配信には、Spring Boot の静的コンテンツ配信機能を活用しました。
この構成により、アプリケーション全体を単一の JAR ファイルにパッケージングし、1 つの Docker コンテナで運用することが可能になります。これにより、開発からデプロイまでの全プロセスの効率化を実現しました。
フロントエンド
- Vue.js 3.2.47
- Node.js 18.14.1
- npm 9.6.7
- Jest 29.5.0
- Webpack 5.82.0
- Babel 7.21.8
- Bulma 0.9.4
フロントエンド開発においては、基礎を着実に築くことを重視しました。そのため、バックエンドで動的に Web ページを生成し、DOM 操作が必要な部分のみを Vue.js を利用して構築するという構成を採用しました。
Vue.js を選択した主な理由は、公式ドキュメントが充実していることと、再利用可能な UI コンポーネントの作成や管理がしやすいことです。
さらに、開発言語の選択においても学習を重視しました。動的型付け言語での開発経験がなかったため、TypeScript ではなく JavaScript を使用することで、新しい言語パラダイムに慣れる機会としました。
インフラ
- AWS (ECR, ECS (Fargate), RDS (MySQL), S3, SES, CloudFront, ALB, Route 53, Systems Manager, CloudWatch)
- Terraform
- CircleCI
- Docker / docker-compose
AWS
本アプリケーションでは、AWS の様々なサービスを使用しています。主要なコンポーネントとしては、AWS ECS (Fargate) を採用しました。
採用の主な理由は以下の 2 点です。
- ローカル環境と本番環境で同一の条件でアプリケーションを実行できること
- 物理サーバーの管理を意識せずに運用できること
コンテナイメージの管理には ECR を採用することで、デプロイメントの効率化を図りました。
なお、環境変数の管理には Systems Manager を導入して、本番環境における設定値を集中管理し、ECS タスクの起動時にこれらの値を取得するように設定しています。
S3 は、ユーザーがアップロードした画像の保存に使用し、CloudFront と組み合わせることで、独自ドメインを使用した HTTPS 通信による高速な画像配信を実現しました。メールサーバーには SES を採用し、主にお問い合わせ時の確認メール送信に利用しています。
開発環境では、本番環境との差異を最小限に抑えるため、MinIO を使用して S3 の機能をシミュレートし、MailHog を導入してメール送受信の確認テストを行っています。
Terraform
AWS 環境を一貫して迅速に構築するために、IaC ツールとして Terraform を導入しました。
本番環境へのデプロイが初めてということもあり、開発段階から本番環境における動作確認を適宜行いたいと考えていました。一方で、開発中はリソースを常時稼働させる必要がないため、必要な時にのみ環境を立ち上げることでコストを削減したいという思いもありました。
これらの要件を満たすために、Terraform を使用してインフラをコードで管理し、一貫した環境を迅速に構築・破棄できるようにしました。
IaC ツールの選定にあたっては、AWS CloudFormation も検討しましたが、Terraform の方が学習曲線が比較的緩やかで理解しやすく、ドキュメントも充実していると感じたため、こちらを採用しました。
CircleCI
CI/CD パイプラインの実現には CircleCI を採用しました。
主な採用理由は、GitHub との優れた統合性と AWS ECS へのデプロイ自動化の容易さです。
運用面では、develop ブランチへのプッシュ時に自動テストを実行し、コードの品質を継続的に確保しています。また、main ブランチへのマージ時には AWS 環境への自動デプロイを行う設定を実装しました。
この CI/CD パイプラインの導入により、開発からデプロイまでのプロセスが大幅に効率化され、同時に人為的ミスのリスクも低減しています。
反省点や今後の課題
■ バックエンド
バックエンドの開発においては、戦術的パターンを中心にドメイン駆動設計を取り入れました。このアプローチを採用することで、ビジネスロジックを反映したコードの重要性や、テストのしやすさを向上させる手法についてなど、多くを学ぶことができました。
しかし、正直なところ、ドメイン駆動設計を完全に理解しきれていないという不安があります。設計を進めていく中で、過剰な複雑性を生んでいるのではないかと感じることが多く、どのようにバランスを取れば良いのか悩む場面が何度もありました。試行錯誤を繰り返しながらも答えを出せず、まだまだ学ぶべきことが多いと感じています。
根拠と自信を持ってより適切な実装ができるように、引き続き学習を進めたいと思います。
■ フロントエンド
開発中はバックエンドの技術面に重点を置きすぎるあまり、フロントエンド、ひいてはユーザビリティに対する認識が甘かった部分がありました。
当然ながら、利用者からは「もう少し直感的な画面表示にしてほしい」や「画像の表示速度をもっと速くしてほしい」など、ユーザーインターフェースや操作性に関する意見が多く寄せられ、その重要性を痛感しました。
また、そのフィードバックを元にフロントエンドを改善する際には、変更がバックエンドにまで影響し、予想以上に修正範囲が拡大することがありました。システム全体の設計をもっと慎重に検討すべきだったと反省しています。
■ 機能
機能面に関しても、まだまだ課題があると感じています。
例えば、共有機能におけるユーザー検索では、ユーザー名を利用していますが、現在の設計では重複を許容していないため、ユーザー名の選択肢が非常に制限されます。
しかし、このアプリケーションは家族などの閉じたコミュニティでの使用を想定しているため、お父さんやお母さんなどのコミュニティ内での役割を示すユーザー名が好まれる可能性が高いと予想されます。
この問題に対処するため、メールによる招待やメールアドレスを利用したユーザー検索などの解決策を検討していますが、プライバシーやセキュリティの観点を考慮すると判断が難しく、改善には至っていません。
また、ログや監視、パフォーマンス最適化などの非機能要件についても十分に対応できていないと感じているため、学習を重ね、改善に努めていきたいと考えています。
さいごに
長文となってしまいましたが、最後までお読みいただき、ありがとうございました。
Web アプリケーションの開発やこうした文章を書くことが初めてということもあり、拙い部分があったかもしれません。
情報の正確性を最優先に考え、要約や整理に努めましたが、不正確な点や読みづらい箇所がありましたら申し訳ありません。
ご意見やアドバイスなどがありましたら、ぜひコメントをいただければ幸いです。