
業務改善とは、業務の流れや処理方法を見直してボトルネックの発見と解消を行う取り組みのことです。多くの企業が取り組んでおり、結果を出して...
目次
こんにちは!マーケターから未経験のエンジニアに転身した社員Tです。
開発を始めて1年が経ち、サーバーサイド開発にも触れるようになりました。
今回は、Javaを使用したダッシュボードAPIの作成についてご紹介します。
これまでクライアントサイド中心の開発を行っていた私が、サーバーサイドのAPI開発に挑戦していく様子をお届けします。
クライアントサイドしか触れてこなかった私は、サーバーサイドについては全くの初心者でした。そこで、まずは「Javaとは何か?」というところからお話ししたいと思います。
Javaは、非常に人気のあるオブジェクト指向プログラミング言語で、さまざまなアプリケーションの開発に広く使用されています。特に、サーバーサイド開発や大規模システム開発において強力なツールとなっており、以下のような特徴があります。
Javaで開発されたアプリケーションは、どのOSでも実行可能で、クロスプラットフォームで動作します。これにより、Javaで書かれたコードは、Windows、Mac、Linuxなど、異なる環境でも問題なく実行できます。
Javaはオブジェクト指向プログラミングに基づいているため、コードが整理されていて、再利用性が高く、保守性に優れています。これにより、アプリケーションの規模が大きくなっても、管理がしやすくなります。
Javaには、開発を効率化するための豊富なライブラリやフレームワークが存在します。特に、Spring BootやHibernateなどは、サーバーサイド開発において非常に便利で、効率的に開発を進めることができます。
このように、Javaは非常に強力で多用途な言語です。これからサーバーサイド開発に取り組む際に、大きな武器となってくれること間違いなしです。
環境構築に関しては、内容が広範囲にわたるため、別の記事で詳細に解説する予定です。
今回作成したいのは、こんなダッシュボード画面です。ユーザーがコースを選択することで、自分の学習状況を一目で確認できるようにすることを目指しています。

全体の作成の流れとしては、下記の通りとなります。
今回は、その中でも最初のステップとして、
「1.ユーザー情報を取得」するAPIの実装について詳しく触れていきたいと思います。
まず最初に、ユーザー情報を取得するAPIの設計方針を考えます。
今回のAPIでは、指定されたメールアドレスを基に、ユーザーIDと法人IDを取得し、画面に返すことが目的です。従って、POSTメソッドを使用し、リクエストボディからメールアドレスを受け取るシンプルなAPI設計にします。
エンドポイント
/top/getUserDetailsByEmail
リクエスト例
{
mailAddress: "test@sms-datatech.co.jp"
}
今回はパスパラメータではなく、リクエストボディからメールアドレスを受け取る設計にしています。これにより、柔軟なデータの受け渡しが可能となります。
レスポンス例
{
"userId":175,
"corporateId":1,
"errorMessageList":null
}
このレスポンスでは、指定されたメールアドレスに対応するユーザーの情報(ユーザーIDと法人ID)をJSON形式で返します。もしエラーが発生した場合は、errorMessageListにエラーメッセージを追加することで、エラー対応を行います。
次に、このAPIを実際に実装していきます。Spring Bootを使って実装を行い、リクエストを受け取った後にデータベースからユーザー情報を取得し、レスポンスを返す流れになります。
まず最初に、ユーザー情報を格納するためのエンティティクラスを作成します。このクラスは、データベースのテーブルとマッピングされ、ユーザー情報を格納する役割を持ちます。ここで定義したクラスを基に、データベース操作を行います。
例として、LoginHistoryEntityエンティティクラスを作成します。
package jp.co.smsdatatech.questiongenerate.entity;
import java.io.Serializable;
import java.sql.Timestamp;
import lombok.Data;
/**
* ログイン履歴テーブルEntity
*/
@Data
public class LoginHistoryEntity implements Serializable {
/** シリアルバージョンUID */
private static final long serialVersionUID = 1L;
/** ユーザーID */
private Integer userId;
/** 法人ID */
private Integer corporateID;
/** ログイン日時 */
private Timestamp loginDate;
}
Serializable インターフェースを実装することで、オブジェクトのシリアライズが可能となり、永続化や通信に利用できるようになっています。
次に、データベースからユーザー情報を取得するためのクエリを定義するXMLファイルを作成します。このXMLファイルは、MyBatisなどのORMツールと組み合わせてデータベースとのやり取りを行います。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="jp.co.smsdatatech.questiongenerate.repository.CustomerMapper">
<select id="getByMailAddress" parameterType="String" resultType="jp.co.smsdatatech.questiongenerate.entity.CustomerEntity">
SELECT
user_id,
user_name,
mail_address,
address_prefecture,
address_municipality,
birthday,
gender,
create_date,
update_date
FROM
customer
WHERE
mail_address = #{mailAddress}
</select>
</mapper>
#{mailAddress}: この部分が、実際に呼び出し元から渡された email パラメータの値に置き換えられます。
次に、サービスクラスを作成してユーザー情報の取得処理を実装します。このクラスでは、リポジトリやMapperを使ってデータベースから情報を取得し、必要に応じてレスポンスに変換します。
import lombok.extern.slf4j.Slf4j;
import jp.co.smsdatatech.questiongenerate.util.DateUtils;
/**
* ログイン履歴テーブルサービス
*/
@Slf4j
@Service
public class LoginHistoryService {
private final LoginHistoryMapper loginHistoryMapper;
// コンストラインジェクションを使用
@Autowired
public LoginHistoryService(LoginHistoryMapper loginHistoryMapper) {
this.loginHistoryMapper = loginHistoryMapper;
}
/**
* 指定されたメールアドレスに基づいてユーザー情報を取得する。
*
* @param email メールアドレス
* @return ユーザーIDと法人IDを含むリスト。
*/
@Transactional(readOnly = true)
public Map<String, Object> getUserDetailsByEmail(String email) {
log.debug("format: メールアドレス {} に基づくユーザー情報を取得します。", email);
return loginHistoryMapper.getUserDetailsByEmail(email);
}
getUserDetailsByEmail メソッドは、@Transactional アノテーションを使用してトランザクションを管理し、指定されたメールアドレスに基づいてユーザー情報を取得します。
最後に、リクエストを処理するコントローラークラスを作成します。このクラスで、リクエストボディからメールアドレスを受け取り、サービスクラスを呼び出して情報を取得し、その情報をレスポンスとして返します。
例えば、TopRestクラスを以下のように作成します。
public class TopRest {
/**
* ログイン状況取得。
*
*<p>login_historyテーブルからユーザーIDに基づくログイン状況を取得し、LoginHistoryResponseFormに詰めて返却する。
*
*<h3>処理フロー</h3>
* 1. ログイン履歴を取得
* 2. 取得データを返却
*
*<h3>レスポンス構成</h3>
* "loginHistorysByDay" -> 曜日ごとのログイン状況 (キー:曜日, 値:true/false)
* "error" -> エラー発生時のメッセージ
*
*@param form Top画面UserId用form
*@param result バリデーション結果
*@return ログイン履歴情報 LoginHistoryResponseform
*/
@PostMapping("/getLoginHistoryByUserId")
public LoginHistoryResponseForm getLoginHistoryByUserId(@RequestBody @Valid UserIdForm form, BindingResult result) {
List<String> errorMessageList = new ArrayList<>();
LoginHistoryResponseForm responseForm = new LoginHistoryResponseForm();
Set<String> processedFields = new HashSet<>();
// バリデーションエラーがある場合、詳細なエラーメッセージを追加
if (result.hasErrors()) {
for (FieldError fieldError : result.getFieldErrors()) {
// フィールド名を基に重複チェック
if (!processedFields.contains(fieldError.getField())) {
String errorMessage = messageSource.getMessage(fieldError, Locale.getDefault());
errorMessageList.add(errorMessage);
processedFields.add(fieldError.getField());
}
}
responseForm.setErrorMessageList(errorMessageList);
public class TopRest {
public LoginHistoryResponseForm getLoginHistoryByUserId(@RequestBody @Valid UserIdForm form, BindingResult result) {
LoginHistoryResponseForm responseForm = new LoginHistoryResponseForm();
Set<String> processedFields = new HashSet<>();
// バリデーションエラーがある場合、詳細なエラーメッセージを追加
if (result.hasErrors()) {
for (FieldError fieldError : result.getFieldErrors()) {
// フィールド名を基に重複チェック
if (!processedFields.contains(fieldError.getField())) {
String errorMessage = messageSource.getMessage(fieldError, Locale.getDefault());
errorMessageList.add(errorMessage);
processedFields.add(fieldError.getField());
}
}
responseForm.setErrorMessageList(errorMessageList);
return responseForm;
}
// ユーザーIDを整数に変換
Integer userId = Integer.parseInt(form.getUserId());
// サービス層で曜日ごとのログイン履歴を取得
Map<String, Boolean> dailyLoginHistoryMap = loginHistoryService.getLoginHistoryByUserIdAndDateRange(userId);
// ログイン履歴が空かどうかをチェック
if (dailyLoginHistoryMap.isEmpty()) {
errorMessageList.add(messageSource.getMessage(ErrorMessages.NO_LOGIN_HISTORY, args:null, Locale.getDefault()));
responseForm.setErrorMessageList(errorMessageList);
return responseForm;
}
responseForm.setDailyLoginHistoryMap(dailyLoginHistoryMap);
return responseForm;
このメソッドは、リクエストボディで受け取ったユーザーID(form)に対してバリデーションを実行します。
もしバリデーションエラーがあれば、そのエラーメッセージをリストに追加し、LoginHistoryResponseForm にセットしてエラーレスポンスとして返却します。
バリデーションエラーがなければ、ユーザーIDを整数に変換し、loginHistoryService.getLoginHistoryByUserIdAndDateRange を使用して、指定されたユーザーの曜日ごとのログイン履歴を取得します。
もしログイン履歴が存在しない場合は、エラーメッセージをリストに追加し、早期リターンします。
ログイン履歴が取得できた場合は、その情報をレスポンスフォームに設定し、正常なレスポンスとして返却します。
Mapの便利さゆえに使用しがちですが、型安全性の欠如やコードの可読性を考慮すると、DTOやEnumを利用した実装に統一していく方が望ましいと感じました。
また、作成中に感じた点として、サーバーサイドに情報を送信する際にリクエストが改ざんされる可能性があるというセキュリティ上の懸念が浮き彫りになりました。特に、クライアントサイドから送信されたデータがそのままサーバーで利用されるため、不正な操作を防ぐためにセキュリティ対策を強化する必要があると痛感しました。この点については、Javaのフィルターや認証・認可の仕組みを活用し、より安全なシステムを構築するという課題が残りました。次回制作時には、このセキュリティ部分を改良していきたいと思います。
まだ知識が浅く、試行錯誤しながら進めている部分も多いですが、実装を通じて多くのことを学びました。今後は、より洗練された設計を目指し、コードの可読性や保守性を高めるための改善に取り組んでいきたいと思います。また、セキュリティやパフォーマンスに関する課題も意識し、これらの分野についてさらに学び、システム全体をより堅牢で効率的なものにしていきたいと考えています。