
BtoBマーケティングにおいて、競合調査は重要ですが時間がかかる業務の代表格です。競合の機能、料金、訴求メッセージ、CTA、導入事例の...
目次
こんにちは!マーケターから未経験のエンジニアに転職した社員Tです。
開発を始めて1年が経ち、サーバーサイド開発にも触れるようになりました。
今回は、Javaを使用したダッシュボードAPIの作成についてご紹介します。
これまでフロントエンド中心の開発を行っていた私が、サーバーサイドのAPI開発に挑戦していく様子をお届けします。
Javaは、非常に人気のあるオブジェクト指向プログラミング言語で、さまざまなアプリケーションの開発に広く使用されています。
詳しい説明については、過去のブログ記事をご参照ください。
環境構築に関しては、内容が広範囲にわたるため、別の記事で詳細に解説する予定です。
今回作成したいのは、こんなダッシュボード画面です。ユーザーがコースを選択することで、自分の学習状況を一目で確認できるようにすることを目指しています。
全体の作成の流れとしては、下記の通りとなります。
今回は、2.コース情報を取得するAPIIの実装について詳しく触れていきたいと思います。
1. ユーザー情報の取得については、過去のブログ記事をご参考ください。
取得したユーザー情報から法人IDをリクエストに含め、コースリストを取得する
/top/getCourseListByCorporateId
{ "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は、法人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 を作成します。この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負荷の軽減が期待できると考えています。
これについては、今後の改善点として検討していく予定です。
今回の実装を通じて、実装前に視野を広げ、さまざまな手段を自分の引き出しに入れておく重要性を実感しました。そのためには、「もっと良い方法はないか」と常にベストを探し続けることが大切だと感じました。経験を積むことと同時に、探究心を持ち続けることで、自然と「より良いものを作りたい」という気持ちが湧いてきます。今後もこの姿勢を大切にし、より良い設計と実装を目指して学び続けていきたいと思います。