バックエンドエンジニアリング部の新卒向け研修に取り組んだ話
はじめに
こんにちは、クラウドエース バックエンドエンジニアリング部の野村です。
この記事では、バックエンドエンジニアリング部で新卒向けの研修として行われたアプリケーション構築課題を開発経験がほぼない私が実施したときの感想や何を学んだかを一つの記事にします。
アプリケーション構築課題とは?
私たちが新卒社員向けの研修課題として取り組んだのは、特定の要件を満たす ToDo アプリケーションの開発でした。この課題では、設計の段階から実装までを一貫して行い、実際の開発フローを体験しました。なお、今回の開発スコープはバックエンドのみで、フロントエンドの開発は含まれていません。
必要な成果物
開発にあたっては以下の成果物が必要となりました。
各種設計書
- 実装したアプリ
- システム構成図
- 画面設計書
- API 設計書
- ER 図
- テーブル定義書
- テスト仕様書
ToDo アプリ実装時の要件
ToDo アプリが満たす要件は次の様になります。
優先度 | 要件内容 |
---|---|
1 | 自分のタスクを簡単に登録/閲覧/更新/削除( CRUD )出来るようにしたい |
2 | ステータス(未着手・着手・完了)を管理したい |
3 | タスクに優先順位をつけたい |
4 | タスクに終了期限を設定できるようにしたい |
5 | ステータスでタスクを絞り込みたい |
6 | タスク名・タスクの説明文でタスクを検索したい |
7 | 一覧画面で(優先順位、終了期限などを元にして)ソートしたい |
8 | 一覧画面にページネーションを付けたい |
9 | タスクにラベルなどをつけて分類したい |
設計
設計書の作成という経験が無かったので様々な資料を参考に一から調べて作成していきました。
画面設計書
最初にどの設計書から作成しようか考えたときに一番完成図が想像しやすい画面設計書に着手しました。
今作った画面設計書を見直すと、この画面を作るためにバックエンドでどのような機能が必要かがきちんと考えられるようになったことは、自分が成長した証だと感じます。
ER 図、テーブル定義書
アプリケーション構築課題の前に行った SQL 研修の経験を元にユーザー、タスク、タスクに紐づくラベルのテーブルとそれぞれの関係性を ER 図にまとめ、必要なカラムの定義をテーブル定義書に記載しました。
API 設計書
全ての設計書の中で最も難易度が高かったのが、API 設計書でした。
API については「異なるソフトウェアやプログラム間で情報をやり取りするための窓口」という知識はあったものの、それを設計し実装する工程を自分が行うとは思ってもいませんでした。
初めはエンドポイント、HTTP メソッド、パラメータ、レスポンスなどの各要素の理解が不十分なまま設計書を作成しましたが、アプリ実装後に API を使ったリクエストのやり取りを実際に行い、それに合わせて修正を重ねることで、API 設計と実装両方の理解を深めることができました。
使用ツール
言語: Java ( Spring Boot )
以前の研修で Java を学習していたこともあり、言語は Java を選んでいます。
フレームワークは RESTful API の開発に強い Spring Boot を採用しました。
ビルドツール: Maven
ビルドツールは Maven と Gradle が主流ですが、ビルドスクリプトで学習したことのない Groovy や Kotlin を扱う Gradle よりも XML を使う Maven を選択しました。
データベース: MySQL ( version 8.0.26 )
IDE: Visual Studio Code
バージョン管理: GitHub
GitHubFlow を用いた開発を経験するために GitHub を採用しています。
テストツール: Mockito (単体テスト)、JUnit4 (結合テスト)
開発に必要な知識
今回の開発では RESTful API の実装と、リクエストを処理するビジネスロジックの実装がメインになりました。ここで各要素について改めて記述していきたいと思います。
RESTful API について
RESTful API は、Web システムの外部からアクセスするための API の一種で、これは REST という設計原則に基づいています。
RESTful API は以下のような特徴を持ちます。
-
アドレス可能性: アクセスする対象となるデータやサービスをリソースとして表現します。各リソースは一意の URI によって識別されます。
-
ステートレス性: 各リクエストはそれ自体で完結し、サーバーはクライアントの状態を保持しません。
-
統一インターフェース: RESTful API は、一貫したインターフェースを提供します。
-
接続性:リソース間の接続を表現することが可能です。これにより、リソースの関連性やナビゲーションを容易にすることができます。
Spring Boot での RESTful API 開発
Spring Boot では、RESTful API の開発を簡単に行うことができます。
その理由は、Spring Boot が提供するアノテーションベースの設定によるものです。
特に重要なのが、@RestController
と@RequestMapping
というアノテーションです。
@RestController
アノテーションは、クラスが RESTful な Web サービスを提供するコントローラであることを示します。このアノテーションが付けられたクラスでは、メソッドの戻り値がレスポンスのボディになります。
一方、@RequestMapping
アノテーションは、特定の URI に対するリクエストを処理するメソッドをマッピングします。このアノテーションをメソッドに適用することで、そのメソッドが特定の HTTP メソッド( GET、POST、PUT、DELETE など)に対するリクエストを処理することを示すことができます。
開発内容
Spring Boot プロジェクトの作成
- まず、Visual Studio code 上で以下の拡張機能などをインストールします。
-
エクスプローラにて「 Create Java Project 」が表示されるのでこれをクリックします
-
Spring Boot を選択すると、Spring Initializr による新しい Spring Boot プロジェクトの作成プロセスが始まります。Spring Initializr は、開発環境に依存せずに Spring Boot プロジェクトの初期設定を生成できるツールです。
ディレクトリ構成
Spring Boot によるアプリケーション開発時、一般的に使われるディレクトリ配置は次のような構成が多いため、同様の構成で作成します。
.
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── demo
│ │ │ ├── DemoApplication.java
│ │ │ ├── controller
│ │ │ ├── service
│ │ │ ├── repository
│ │ │ └── entity
│ │ └── resources
│ │ ├── application.properties
│ │ ├── static
│ │ ├── schema.sql
│ │ └── templates
│ └── test
│ └── java
│ └── com
│ └── example
│ └── myproject
├── pom.xml
└── README.md
DemoApplication.java
アプリケーションが起動するときに最初に実行されるアプリケーションの中心的なファイルです。
Spring Boot アプリケーションであることを示すアノテーションである @SpringBootApplication
が付いています。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
今回のアプリケーション構築課題では、このファイルを直接的に編集することはあまりありませんでした。
ただ、一つ重要な点としてこのファイルはプロジェクトのルートパッケージ(ここでは com.example.myproject )に位置しており、Spring がこのパッケージとその下位パッケージに存在するコンポーネントを自動的にスキャンします。
アプリケーション構築課題の時にはこの後作成する各ディレクトリについて、最初にこのコンポーネントスキャンを意識せずにディレクトリを作成してしまったために途中で大幅なディレクトリ配置の修正を余儀なくされてしまいました。
controller
アプリケーションのエンドポイントを定義し、ユーザーからのリクエストを適切に振り分けます。
今回は先ほど説明した @RestController
アノテーションを用いて RestfulAPI を作成していきます。
service
Service は、Spring Boot アプリケーションの中心的な部分で、ビジネスロジック(業務処理)を実装します。
Controller はクライアントからのリクエストをルーティングし、そのリクエストの処理を service が行い、処理した結果を再び Controller がクライアントに返す。
これが Spring Boot の基本的な処理の流れになります。
repository
Repository は、データベースとの接続を担当し、データの取得や保存を行う部分です。 Spring Boot では JpaRepository というインターフェースを提供しており、今回はこれを継承することでデータベースに対する操作をより簡単に行うことができました。
entity
データベースのテーブルを表現するクラスを配置します。
つまり、データベース内の一つのテーブルを Java のクラスとして表現したものになります。この Entity クラスを通じて、データベースのテーブルと Java プログラムの間でデータのやり取りが行われます。
resources/application.properties
アプリケーションの設定を管理するためのファイルです。今回の課題ではデータベースへの接続情報などを設定していきました。
pom.xml
プロジェクトのビルド設定や依存関係を管理するファイルです。 Spring Initializr でプロジェクトを作成する際に指定したツールが依存関係としてここに記載されています。
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myproject</artifactId>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0-M2</version>
</parent>
</project>
schema.sql
データベースのスキーマを定義するために使用されます。Spring Boot アプリケーションが起動する際にこのファイルに記述された SQL ステートメントが実行されてデータベースの構造が自動的に作成されます。
デモ
要件 1 自分のタスクを簡単に登録/閲覧/更新/削除( CRUD )出来るようにしたい を通してアプリ開発の流れを簡単に振り返っていきます。
- schema.sql の作成
先にデータベースに関する作業を済ませたいと思います。今回は Task テーブルを作成します。
CREATE TABLE IF NOT EXISTS Task( --Taskテーブルを作成する。もし既に存在していたら、新しく作成しない。
id INT AUTO_INCREMENT, --AUTO_INCREMENTをつけることで、新しいデータが追加されるたびに自動的に1ずつ増えていく。
task_name VARCHAR(255) NOT NULL,
task_detail VARCHAR(255) NOT NULL,
PRIMARY KEY(id) --idをプライマリーキーに設定
);
- application.properties の作成
/resources 配下のディレクトリに application.properties を作成し、設定を記述していきます。
spring.datasource.url = jdbc:mysql://localhost:3306/mydb //データベースへの接続URLを指定
spring.datasource.username = user
spring.datasource.password = password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver //JDBCドライバー(Javaからデータベースに接続するためのAPI)のクラス名を指定する
spring.sql.init.mode=always // データベースの初期化モードを指定
- Entity の作成
src/main/java/com/demo の下に entity ディレクトリを作成し、Task クラスを作成します。
package com.example.demo.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.Data;
@Entity
@Data
public class Task {
@Id
private int id;
private String task_name;
private String task_detail;
}
このTask
エンティティクラスは、先ほどschema.sql
で作成したTask
テーブルの各カラムに対応します。@Id
アノテーションがついているid
は、テーブルのプライマリーキーに対応します。また、task_name
とtask_detail
はそれぞれテーブルの同名のカラムに対応します。
- Controller の作成
Controller を作成していきます。 controller ディレクトリを作成し、TaskController クラスを作成していきます。@RestController
で指定し、 service による処理を返すことで簡単に API を作成することができます。
package com.example.demo.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.entity.Task;
import com.example.demo.service.TaskService;
@CrossOrigin(origins = "http://localhost:8080") // localhost:8080からのクロスオリジンリクエストを許可する
@RestController // このクラスがRESTfulなWebサービスのコントローラーであることを示す
public class TaskController {
@Autowired // Springが自動的に必要なオブジェクトをこのフィールドに注入
TaskService taskService;
@GetMapping("/task") // HTTP GETリクエストを/taskのURLにマッピングする
List<Task> getTasks(){ // すべてのタスクを取得するメソッド
return taskService.findAllTask(); // タスクサービスを使用して全てのタスクを見つけて返す
}
@PostMapping("/task") // HTTP POSTリクエストを/taskのURLにマッピングする
Task postTask(@RequestBody Task task){ // 新しいタスクを作成するメソッド
return taskService.postTask(task); // タスクサービスを使用してタスクを保存し、結果を返す
}
@PutMapping("/task/{id}") // HTTP PUTリクエストを/task/{id}のURLにマッピングする
Task putTask(@RequestBody Task task, @PathVariable int id){ // 既存のタスクを更新するメソッド
return taskService.putTask(task, id); // タスクサービスを使用して指定されたIDのタスクを更新し、結果を返す
}
@DeleteMapping("/task/{id}") // HTTP DELETEリクエストを/task/{id}のURLにマッピングする
void deleteTask(@PathVariable int id){ // 既存のタスクを削除するメソッド
taskService.deleteTask(id); // タスクサービスを使用して指定されたIDのタスクを削除する
}
}
- Service の作成
Controller がリクエストを受け取った後の業務処理を担う Service です。Service は Interface として定義され、その具体的な実装は ServiceImpl によって提供されます。
package com.example.demo.service;
import java.util.List;
import com.example.demo.entity.Task;
public interface TaskService { // TaskServiceインターフェースの定義。Taskに関する操作を定義したインターフェース。
List<Task> findAllTask();
Task postTask(Task task);
Task putTask(Task task,int id);
void deleteTask(int id);
}
- ServiceImpl の作成
Service クラスの実際の処理を記述していきます。個々の処理と処理結果を taskRepository を使ってデータベースに反映するコードを記述します。
package com.example.demo.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.entity.Task;
import com.example.demo.repository.TaskRepository;
@Service // Springが管理するサービス層のクラスであるを表す
public class TaskServiceImpl implements TaskService {
@Autowired // Springが自動的にこの部分にTaskRepositoryクラスのインスタンスを設定する
private TaskRepository taskRepository;
@Override // 親クラスのメソッドを上書き(オーバーライド)することを示す
public List<Task> findAllTask(){ // 全てのタスクを取得するメソッド
return taskRepository.findAll(); // データベースから全てのタスクを取得する
}
@Override
public Task postTask(Task task){ // 新しいタスクを作成するメソッド
return taskRepository.save(task); // データベースに新しいタスクを保存する
}
@Override
public Task putTask(Task task,int id){ // 既存のタスクを更新するメソッド
Task updatedTask = taskRepository.findById(id).orElse(null); // IDでタスクを検索し、存在しない場合はnullを返す
updatedTask.setTask_name(task.getTask_name()); // タスクの名前を更新する
updatedTask.setTask_detail(task.getTask_detail()); // タスクの詳細を更新する
taskRepository.save(updatedTask); // データベースに更新したタスクを保存する
return updatedTask; // 更新したタスクを返す
}
@Override
public void deleteTask(int id){ // タスクを削除するメソッド
taskRepository.deleteById(id); // IDで指定したタスクをデータベースから削除する
}
}
- TaskRepository の作成
JpaRepository を継承した TaskRepository インターフェースを作成します。これにより、データベースに対する基本的な CRUD 操作が自動的に提供されます。
package com.example.demo.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.example.demo.entity.Task;
@Repository
public interface TaskRepository extends JpaRepository<Task,Integer> {
}
アプリの起動とリクエストの送信によるテスト
作成した Spring Boot アプリを起動してみます。
Visual Studio code のサイドバーから実行とデバックを選択します。
実行ボタンを押すことで、Spring Boot アプリを起動することができます。
リクエストの送信によるテスト
次に、Postman を用いて、HTTP リクエストを送信し、その結果を確認します。
Postman は API のエンドポイントへのリクエストの送信とレスポンスの確認を簡単に行うことができ、これにより、API の挙動を確認し、デバッグを行うことができます。
GET リクエストのテスト
まず初めに GET リクエストから試してみます。
以下のようなデータを Task テーブルに事前に登録した状態で、localhost の 8080 ポートに接続し、GET リクエストを送信します。
Task テーブル
task_id | task_name | task_detail |
---|---|---|
1 | test_name1 | task_detail1 |
すると、ステータスコード 200 と共に現在データベースに登録されているタスクがレスポンスとして返ってきます。
POST リクエストのテスト
次に POST リクエストで新しいタスクを登録してみます。
リクエストのヘッダーで Content-Type に application/json を指定することで、送信されるデータが JSON 形式 であることを示します。
リクエストのボディには、登録したいタスクの内容を JSON 形式で記載します。今回は id が 4 のタスクを新規作成してみます。
Send ボタンを押すと新規に登録したタスクがレスポンスとして返ってきます。
PUT リクエストのテスト
次に PUT リクエストで今登録した id 4 のタスクを更新してみます。
URL の task の後に更新したいタスクの id を指定し、更新内容をボディに記述します。
レスポンスとして、更新されたタスクが返ってきます。
DELETE リクエストのテスト
最後に DELETE リクエストです。
まず、現在登録されているタスクの状況を GET メソッドを使用して確認します。
先ほど登録した id 4 のタスクが表示されています。
このタスクを DELETE リクエストで削除します。
URL の task の後に削除したい task の id を指定して DELETE リクエストを送信します。
送信後にもう一度 GET リクエストを送信し、タスクの状態を確認します。
id 4 が削除されているのがわかると思います。
以上で要件 1 の基本的な CRUD 操作の実装は終わりです。課題ではここから要件ごとに機能を追加していく形で開発を進めていきました。
終わりに
アプリケーション構築課題は未知の事ばかりでメンターの方の指導やネットの記事を参考にしながら、一歩一歩少しずつ作業を進めていきました。最初は全くわからなかった Spring Boot も、最終的には自分で今回の ToDo アプリケーションを作成できるまでになりました。この経験は、自分にとって確かな積み重ねになったと感じています。
最後まで読んでいただきありがとうございました。
Discussion