【はじめてのJava】ダッシュボードAPIを作成しよう!②コース情報取得編

はじめに

こんにちは!マーケターから未経験のエンジニアに転職した社員Tです。
開発を始めて1年が経ち、サーバーサイド開発にも触れるようになりました。

今回は、Javaを使用したダッシュボードAPIの作成についてご紹介します。
これまでフロントエンド中心の開発を行っていた私が、サーバーサイドのAPI開発に挑戦していく様子をお届けします。

本記事の対象の方

  • サーバーサイド開発をこれから学びたい方
  • JavaでのAPI開発に興味がある方
  • ダッシュボードの作成を通じて、実践的な技術を学びたい方

Javaとは?

Javaは、非常に人気のあるオブジェクト指向プログラミング言語で、さまざまなアプリケーションの開発に広く使用されています。

詳しい説明については、過去のブログ記事をご参照ください。

環境構築

環境構築に関しては、内容が広範囲にわたるため、別の記事で詳細に解説する予定です。

ダッシュボードAPIの作成

今回作成したいのは、こんなダッシュボード画面です。ユーザーがコースを選択することで、自分の学習状況を一目で確認できるようにすることを目指しています。

全体の作成の流れとしては、下記の通りとなります。

  1. ユーザー情報を取得
  2. コース情報を取得
  3. 各グラフ描画に必要なデータを取得

今回は、2.コース情報を取得するAPIIの実装について詳しく触れていきたいと思います。

1. ユーザー情報の取得については、過去のブログ記事をご参考ください。

コース情報取得APIの設計

取得したユーザー情報から法人IDをリクエストに含め、コースリストを取得する

エンドポイント

/top/getCourseListByCorporateId
  • HTTPメソッド: POST
  • リクエストボディ: 法人ID
  • レスポンス: エラーメッセージリスト、コースリスト

リクエスト例

{
  "corporateId": 1
}

今回はパスパラメータではなく、リクエストボディから法人IDを受け取る設計にしています。これにより、柔軟なデータの受け渡しが可能となります。

レスポンス例

{
    "errorMessageList": null,
    "courseList": [
        {
            "course_id": 1,
            "corporate_id": 1,
            "course_name": "プログラミング基礎",
            "create_date": "2024-06-01T01:00:00.000+00:00",
            "update_date": "2024-06-01T01:00:00.000+00:00"
        },
        {
            "course_id": 2,
            "corporate_id": 1,
            "course_name": "データベース管理",
            "create_date": "2024-06-15T00:00:00.000+00:00",
            "update_date": "2024-06-15T00:00:00.000+00:00"
        }
    ]
}

レスポンスでは、courseList というフィールドにコース情報のリストが返されます。
このリストには、各コース情報が含まれます。もしエラーメッセージがあれば、errorMessageListにリストとして含まれますが、今回はエラーがない想定です。

コース情報取得APIの実装

今回は、ユーザー情報をもとにコースリストを取得するためのAPIを実装します。このAPIは、法人IDに基づいて、対応するコースの情報を返す役割を果たします。

コース情報を管理するモデルクラスの作成

まず最初に、ユーザー情報を格納するためのエンティティクラスを作成します。
このクラスは、データベースのテーブルとマッピングされ、ユーザー情報を格納する役割を持ちます。ここで定義したクラスを基に、データベース操作を行います。

例えば、以下のような CourseEntity クラスを作成します。

package jp.co.smsdatatech.questiongenerate.entity;

import java.io.Serializable;
import java.sql.Timestamp;

import jakarta.persistence.Column;
import lombok.Data;

/**
 * courseテーブルEntity
 */
@Data
public class CourseEntity implements Serializable {

    /** シリアルバージョンUID */
    private static final long serialVersionUID = 1L;

    /** コースID */
    private Integer course_id;

    /** 法人ID */
    private Integer corporate_id;

    /** コース名 */
    private String course_name;

    /** 作成日 */
    private Timestamp create_date;

    /** 更新日 */
    private Timestamp update_date;
}

MapperXMLの作成

次に、データベースからコース情報を取得するためのクエリを定義する MapperXML を作成します。この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.CourseMapper">
    <select id="getAllByCorporateId" parameterType="Integer" resultType="jp.co.smsdatatech.questiongenerate.entity.CourseEntity">
        SELECT
            course_id,
            corporate_id,
            course_name,
            create_date,
            update_date
        FROM
            course
        WHERE
            corporate_id = #{corporateId}
        ORDER BY
            course_id
    </select>
</mapper>

サービスクラスの作成

次に、サービスクラスを作成してコース情報の取得処理を実装します。このクラスでは、リポジトリや Mapper を使ってデータベースから情報を取得し、レスポンスとして変換します。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import jp.co.smsdatatech.questiongenerate.entity.CourseEntity;
import jp.co.smsdatatech.questiongenerate.repository.CourseMapper;

/**
 * コーステーブルService
 */
@Service
public class CourseService {

    /** ログ出力クラス */
    private static final Logger log = LoggerFactory.getLogger(clazz:CourseService.class);

    /** courseテーブルMapper */
    @Autowired
    private CourseMapper courseMapper;

    /**
     * コース検索(法人ID検索)
     * 1.機能概要
     * courseテーブルから法人IDに紐づくコースを取得し、Listで返却する。
     * 2.処理フロー
     * ①DB接続・抽出
     * ②返却
     *
     * @param corporateId 法人ID
     * @return {@literal List<CourseEntity> コース一覧}
     */
    public List<CourseEntity> getAllByCorporateId(Integer corporateId) {
        List<CourseEntity> courseList = courseMapper.getAllByCorporateId(corporateId);
        log.info("courseテーブルから法人ごとのレコードを取得しました。");
        return courseList;
    }
}

コントローラークラスの作成

最後に、リクエストを処理する TopRest コントローラークラスを作成します。

/**
 * コースリスト取得.
 *
 * 法人IDに紐づくコースリストを取得し、CorporateCourseResponseForm に詰めて返却する。
 *
 * <h3>処理フロー</h3>
 * 1. コース情報(コースIDとコース名)を取得<br>
 * 2. 取得したリストを返却
 *
 * @param form Top画面Form
 * @param result バリデーション結果
 * @return コースリスト CorporateCourseResponseForm
 */
@PostMapping("/top/getCourseListByCorporateId")
public CorporateCourseResponseForm getCourseListByCorporateId(@RequestBody @Valid TopForm form,
        BindingResult result) {
    log.debug("TopRest (getCourseListByCorporateId) 呼び出し開始");

    CorporateCourseResponseForm responseForm = new CorporateCourseResponseForm();
    List<String> errorMessageList = new ArrayList<>();

    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 corporateId = Integer.parseInt(form.getCorporateId());

    // 法人IDを基にコースリストを取得
    List<CourseEntity> courseList = courseService.getAllByCorporateId(corporateId);

    if (CollectionUtils.isEmpty(courseList)) {
        // コースが見つからなかった場合
        errorMessageList.add(messageSource.getMessage(ErrorMessages.COURSE_NOT_FOUND, args:null, Locale.getDefault()));
        responseForm.setErrorMessageList(errorMessageList);
        log.debug("CourseRest (getCourseList) コースが見つかりません: ");
        return responseForm;
    }

    // コースリストをレスポンスに設定
    responseForm.setCourseList(courseList);
    return responseForm;
}

今回の実装では、リクエストボディのバリデーション に @NotBlank と @Pattern のアノテーションを適用することで、不正なデータによる例外発生を未然に防ぐ設計 になっています。

実装内容の振り返り

今回のコース情報取得に関して、情報が頻繁に変更されることはないため、キャッシュの活用が有効であることに実装後に気付きました。
現状ではリクエストごとにDBへのアクセスを行っていますが、キャッシュを導入することで、レスポンス速度の向上やDB負荷の軽減が期待できると考えています。

これについては、今後の改善点として検討していく予定です。

まとめ

今回の実装を通じて、実装前に視野を広げ、さまざまな手段を自分の引き出しに入れておく重要性を実感しました。そのためには、「もっと良い方法はないか」と常にベストを探し続けることが大切だと感じました。経験を積むことと同時に、探究心を持ち続けることで、自然と「より良いものを作りたい」という気持ちが湧いてきます。今後もこの姿勢を大切にし、より良い設計と実装を目指して学び続けていきたいと思います。

筆者イメージ

筆者:K・T(20代)

所属:ハイブリット・マルチクラウド部

マーケターとしてWEB広告運用やアパレルブランドの運営に携わってきたが、エンジニアとしての新たな挑戦を決意した転職組。
好奇心が旺盛で、新しいことに挑戦する姿勢が彼女の魅力。
マーケター×エンジニアの強みを生かし、UI/UXの提案から実装まで幅広く対応し、彼女自身がブランドを育てているのだろうと思わせてくれる、そんな20代の乙女。

まずはお気軽にご相談ください
お問い合わせフォーム

その他のイベント・セミナー 一覧へ