Quarkus で RESTfulAPI を作ってみた

はじめに

初めまして、dodaX エンジニアリンググループの上池です。
dodaX では、マイクロサービス指向でアーキテクチャが設計されており、各サービスは Spring Boot で実装されています。
最近、インフラが Kubernetes ベースに刷新されたこともあり、コンテナ技術に興味が湧いてきました。
そこで今回はコンテナファーストをうたっている Java のフレームワーク Quarkus について調査し、簡単な RESTfulAPI を作ってみました!
Quarkus の特徴や Spring Boot との比較についても解説しています。

この記事は Spring Boot は知っているけれども、Quarkus や GraalVM には初めて触れる方に向けた入門記事です!

TL;DR

  • Quarkusの起動時間は Spring Bootよりも早い
    • Quarkusは起動時ではなくビルド時にBeanのスキャン、依存関係の解決を行っている
    • JITコンパイルが不要な「ネイティブ実行可能ファイル」の生成がサポートされている
      • 設定不要ですぐにネイティブ実行可能ファイルを生成できる
      • Spring Bootも バージョン3.0からネイティブ実行可能ファイルの生成がサポートされているが、Quarkusよりは起動時間が遅い
  • Quarkusアプリケーションの実装方法はSpring Bootとそこまで変わらない
    • QuarkusでもSpring同様にDIとAOPがサポートされている

Quarkus の起動時間

いきなりですが、Quarkus の起動時間と Spring Boot の起動時間を比較してみます。
5 回試行の平均をとってみました。

Application StartUp Time
Quarkus (Native Image) 0.023s
Spring Boot on JVM 1.04s

Quarkus の起動時間、めちゃくちゃ早いです...!!

※後述しますが、ここでは Quarkus アプリケーションをネイティブコンパイルし、「ネイティブ実行可能ファイル」を実行しています。

Quarkus とは

Quarkus とは、RedHat が主導で開発している オープンソースの Web フレームワークです。
Quarkus はコンテナファーストの Web フレームワークとして設計されていて、 起動時間やメモリフットプリントを最適化することに重点が置かれています。
なので、クラウド環境での利用に非常に適していると言えますね!

Quarkus は便利なツールも豊富に備えています。
例えば、QuarkusCLI を使用すれば、アプリケーションの作成からデプロイまで、簡単かつ効率的に行うことができたり、 Kubernetes の設定ファイルを自動作成することなんかもできたりします。
また、Dev UI と呼ばれるツールも存在し、ブラウザからアプリケーションの設定を変更できたり、使用しているライブラリを GUI で見ることができます。

Dev UI

開発者ツールが充実していて開発しやすそうです...!

Quarkus の起動時間が速い理由

冒頭でも記述したように、Quarkus は起動時間が早いです。
では、なぜ起動時間が早いのでしょうか?

ビルド時での最適化

Spring Boot(Spring)の主要な機能に DI(Dependency injection)と AOP(Aspect Oriented Programming) があると思いますが、 Quarkus でも DIAOP がサポートされています。

Spring Boot のDIは 実行時(特に起動時)に Bean のスキャンと依存性の解決が行われ、インスタンス生成および DI コンテナへの登録が行われます。これが Spring Boot の起動に時間がかかる原因の一つです。
しかし、 Quarkus ではこの手続きをビルド時に実行します。
つまり、Quarkus の DI ではビルド時に Bean のスキャンと依存関係の解決を行なっています。

Spring Boot のAOPは、例えば@Transactionalをメソッドに付与すると、Spring は CGLIB を使って動的プロキシクラスを生成します。これは Spring Boot の実行時に行われます。
一方で、Quarkus ではビルド時にプロキシを生成してコンパイルします。
ビルド時にどのクラスが何のプロキシを使用するのかを解析します。静的にプロキシを生成します。

さらに、Spring ではリフレクションを多用していますが、Quarkus はリフレクションをなるべく使わない設計方針を採用しています。 なので、起動時間を短くするための静的解析が着実に行われます。

GraalVM でのネイティブ実行可能ファイル生成をサポートしている

Quarkus は GraalVM での ネイティブ実行可能ファイルの生成をサポートしています。

そもそも、JVM 上で動く Java アプリケーションでは、Java クラスファイルを実行時に JVM がコンパイルしてアプリケーションを実行しています。つまり JIT(Just-In-Time) コンパイルしています。

その点、GraalVM (の中の Graal)を使うと
Java のソースファイルを AOT(Ahead-Of-Time) コンパイルすることができ、実行時に JVM による JIT コンパイルを不要とするネイティブ実行可能ファイルを作成できます。
(GraalVM 自体はネイティブ実行可能ファイル生成だけでなく、JVM として動かすこともできます。)

Quarkus は GraalVM でネイティブ実行可能ファイルを生成することを考慮して設計されているので、開発者は基本的に特に意識することなく Quarkus アプリケーションをネイティブ実行可能ファイルに AOT コンパイルすることができます。

※後述しますが、SpringBoot についても バージョン 3.0 からネイティブ実行可能ファイルの生成がサポートされています。

ということで、Quarkus は起動時間を早くするために以上のようなアプローチを行っています。
起動時間が早くなれば、開発者の生産性が上がる・クラウドリソースをスケールしやすい・サーバレスで使えるなどのメリットがあるので嬉しいですね!

Quarkus で RESTfulAPI を実装してみる

ではこのQuarkus を使って、データを登録・取得できるAPIを実装してみたいと思います。

前提

Java: 21
Quarkus: 3.6.0
OS: macOS 13.3

まずは環境構築をして動作させてみる

Quarkus CLI をインストールする

まずはQuarkus CLIをインストールします。 日本語のドキュメントがあるのはありがたいですね。

brew でインストールします。

brew install quarkusio/tap/quarkus

念のためインストールできたことを確認しておきましょう。

~ $ quarkus --version
3.6.0
~ $ which quarkus
/opt/homebrew/bin/quarkus

これでQuarkus CLIが使えるようになりました。

Quarkus プロジェクトの作成

では、さっそくプロジェクトを作成してみましょう。

~/sandbox $ quarkus create app jp.co.persolcareer:quarkusdemo --gradle
  • このコマンドで以下の指定を行っています。
    • groupId: jpco.persolcareer
    • artifactId: quarkusdemo
    • build tool: gradle

ディレクトリ配下にquarkusdemoプロジェクトが作成されました。

~/sandbox $ ls
quarkusdemo

プロジェクトの中を見てみると、src/main配下にdockerディレクトリが生成されているのが面白いですね!

~/sandbox/quarkusdemo $ ls src/main
docker      java        resources

QuarkusCLIで Quarkus アプリケーションをコンテナイメージにビルドすることができるようで、ここにはそのときに使用される設定が書かれているようです。

起動してみる

プロジェクトを作成した時点で文字列を返すAPIが作成されています。
作成されたアプリを開発モードで起動してみます。

~/sandbox/quarkusdemo $ quarkus dev

お、立ち上がりました。

BUILD SUCCESSFUL in 11s
7 actionable tasks: 6 executed, 1 up-to-date
Listening for transport dt_socket at address: 5005

__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2023-12-05 09:14:44,818 INFO  [io.quarkus] (Quarkus Main Thread) quarkusdemo 1.0.0-SNAPSHOT on JVM (powered by Quarkus 3.6.0) started in 0.833s. Listening on: http://localhost:8080

2023-12-05 09:14:44,820 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2023-12-05 09:14:44,820 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy-reactive, smallrye-context-propagation, vertx]

--
Tests paused
Press [e] to edit command line args (currently ''), [r] to resume testing, [o] Toggle test output, [:] for the terminal, [h] for more options>

http://localhost:8080/hello にリクエストを投げてみます。

~ $ curl -w "\n" http://localhost:8080/hello
Hello from RESTEasy Reactive

レスポンスが返ってきました!

インタフェース を実装する

プロジェクト作成時に生成されているソースを見つつ、編集していきます。 まずはインタフェースを見てみましょう!

プロジェクト作成時にGreetingResource.javaが生成されています。 JakartaREST(旧 JAX-RS) 仕様の実装である RESTEasy を利用して書かれています。

この Resource クラスですが、RESTEasy では裏で CDI を利用しているようです。
Spring Boot(Spring MVC)では、DispacherServletに見つけてもらうために、@RestControllerをつけて、Bean を登録する必要があると思いますが、
Quarkus も同じ感じで @Pathをつけると DI コンテナに登録されます。

GreetingResource.java

package jp.co.persolcareer;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/hello")
public class GreetingResource {

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return "Hello from RESTEasy Reactive";
    }
}

一旦ここはプロジェクト作成時のままにしておきます。

Service クラスを実装する

次に Service クラスを実装してみます。GreetingService.javaを作成してみます。
単純にメッセージを返すだけです。

GreetingService.java

package jp.co.persolcareer.service;

import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class GreetingService {
    static final String message = "Good Morning!";

    String getMessage() {
        return message;
    }
}

DI コンテナに登録するために@ApplicationScopedをつけています。
Spring 同様にスコープは@RequstScoped@SessionScopedなどあるようです。(Quarkus アプリケーションで使用できるスコープ)

Resource クラスから Service クラスを使用するコードに変更します。

@Path("/hello")
public class GreetingResource {

    GreetingService service;

    @Inject
    GreetingResource(GreetingService service) {
        this.service = service;
    }

    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String hello() {
        return service.getMessage();
    }
}

@InjectをつけてGreetingServiceクラスを注入しています。@Injectはコンストラクタが 1 つであれば省略できます。
Quarkus ではコンストラクタインジェクション、セッターインジェクション、フィールドインジェクションが使えるようです。
この辺りは Spring と同様ですね。

動作確認をしてみます。

~ $ curl -w "\n" http://localhost:8080/hello
Good Morning!

正常に動いています!

DB にアクセスする処理を実装する

次は、データベースにアクセスする処理を書いてみます。

今回は postgres のコンテナを使います。
試すだけなのでデータの永続化はしていないです。

docker-compose.yml

version: '3'
services:
  postgres:
    container_name: demo_db
    image: postgres:16
    ports:
      - "5432:5432"
    volumes:
      - ./postgres/init:/docker-entrypoint-initdb.d
    environment:
      POSTGRES_USER: "postgres"
      POSTGRES_PASSWORD: "postgres"
      POSTGRES_DB: demo

コンテナ生成時にメッセージテーブルを作成し、メッセージ("Good Night")を一件入れるようにしておきます。

init.sql

DROP TABLE IF EXISTS message;
CREATE TABLE message (
        id integer NOT NULL PRIMARY KEY,
        body varchar(50) NOT NULL
);
CREATE SEQUENCE message_id_seq START 1;
INSERT INTO message (id, body) VALUES(nextval('message_id_seq'), 'Good Night.');

以下のコマンドで extension を追加します。
dodaX では MyBatis を使っているので、ここでも MyBatis を使ってみます。

quarkus ext add io.quarkus:quarkus-jdbc-postgresql
quarkus ext add io.quarkiverse.mybatis:quarkus-mybatis

build.gradle の dependencies に追加されていますね。

dependencies {
    implementation 'io.quarkiverse.mybatis:quarkus-mybatis:2.2.0'
    implementation 'io.quarkus:quarkus-jdbc-postgresql'
    ...
}

では、データベースにアクセスするコードを書いてみます。( MyBatis ドキュメント)

まずは db の接続設定から。
application.properties

quarkus.datasource.username=postgres
quarkus.datasource.password=postgres

quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/demo
quarkus.datasource.jdbc.max-size=16

次に Entity。
Message.java

package jp.co.persolcareer.entity;

public record Message(Integer id, String body) {
}

次に Mapper。Spring Boot で書くときと変わりません。
MessageMapper.java

package jp.co.persolcareer.mapper;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import jp.co.persolcareer.entity.Message;

@Mapper
public interface MessageMapper {

    @Select("SELECT * FROM message WHERE id = #{id}")
    Message getMessageById(Integer id);

    @Insert("INSERT INTO message (id, body) VALUES (nextval('message_id_seq'), #{body})")
    Integer createMessage(@Param("body") String body);
}

合わせて、Service クラスと Resource クラスを修正します。
GreetingService.java

@ApplicationScoped
public class GreetingService {

    MessageMapper messageMapper;

    GreetingService(MessageMapper messageMapper) {
        this.messageMapper = messageMapper;
    }

    public String getMessageById(Integer id) {
        Message message = messageMapper.getMessageById(id);
        return message.body();
    }
}

GreetingResource.java

    @Path("/{id}")
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String getMessage(@PathParam("id") Integer id) {
        return service.getMessageById(id);
    }

    @POST
    public void createMessage(@FormParam("body") String body) {
        service.createMessage(body);
    }

では動作確認をしてみましょう!

~ $ curl -w "\n" http://localhost:8080/hello/1
Good Night.

POST リクエストも投げてみます!

~ $ curl -X POST -d 'body=How is it going.' http://localhost:8080/hello
~ $ curl -w "\n" http://localhost:8080/hello/2
How is it going.

普通に使えますね...!

トランザクションを使ってみる

次に、トランザクションを使ってみたいと思います。 (Quarkus でのトランザクション)

Quarkus では、Spring Boot と同様に@Transactionalをつけることで宣言的にトランザクションを管理できます。

実際に記述して動かしてみようと思います。

GreetingService.java

    @Transactional
    public void createMessage(String body) {
        messageMapper.createMessage(body);

        if (body.equals("please throw exception.")) {
            throw new RuntimeException("error");
        }
    }

プロダクションでは書かないようなコードですが、確認のためにこんな感じにしました。
まず、データベースにデータを登録してから、
受け取ったbodyplease throw exceptionである場合はRuntimeExceptionを投げるようにしています。
トランザクションが管理されていない場合は、例外が発生する場合でもデータベースにデータが登録されてしまいます。

では動作を確認してみましょう!

~ $ curl -X POST -d 'body=This should be commited.' http://localhost:8080/hello
~ $ curl -w "\n" http://localhost:8080/hello/3
This should be commited.

当然正常に動きますね。
では、please throw exceptionを指定して例外を発生させてみます!

~ $ curl -X POST -d 'body=please throw exception.' http://localhost:8080/hello
{"details":"Error id 243a3985-c04e-4937-aabc-aa4529ed262c-7, java.lang.RuntimeException: error","stack":"java.lang.RuntimeException:
...(省略)

例外が発生しました。トランザクションが管理されていれば、データはデータベースに登録されていないはずです。
確認してみましょう!

~ $ curl -w "\n" http://localhost:8080/hello/4
{"details":"Error id 243a3985-c04e-4937-aabc-aa4529ed262c-9, java.lang.NullPointerException: ","stack":"java.lang.NullPointerException

NullPointerExceptionですね。ちゃんとトランザクション制御されています。

ここまで、Quarkus の実装を試してきましたが
DI・AOP をサポートしていることもあって、書き方については Spring Boot とそこまで大きく変わらない印象ですね。

Quarkus でネイティブ実行可能ファイルを生成して実行してみる

前述したように、Quarkusではネイティブ実行可能ファイルの生成がサポートされているので生成してみたいと思います。 ネイティブ実行可能ファイルの生成

GraalVM のインストール・設定

まずはGraalVMをこちらからダウンロードします。

ダウンロードしたら拡張属性の除去と解凍をし、JavaVirtualMachines配下に移動。

~ $ sudo xattr -r -d com.apple.quarantine graalvm-jdk-21_macos-aarch64_bin.tar.gz
~ $ tar -xzf graalvm-jdk-21_macos-aarch64_bin.tar.gz
~ $ sudo mv graalvm-jdk-21.0.1+12.1 /Library/Java/JavaVirtualMachines

GRAALVM_HOME にpathを設定します。

~ $ export GRAALVM_HOME=/Library/Java/JavaVirtualMachines/graalvm-jdk-21.0.1+12.1/Contents/Home

ビルドして実行する

ネイティブ実行可能ファイルをビルドしてみます。 ビルドに 1 分ほどかかりました。

~/sandbox/quarkusdemo $ quarkus build --native
BUILD SUCCESSFUL in 1m 11s
11 actionable tasks: 3 executed, 8 up-to-date

実行してみます!

~/sandbox/quarkusdemo $ ./build/quarkusdemo-1.0.0-SNAPSHOT-runner
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2023-12-05 09:35:36,583 INFO  [io.quarkus] (main) quarkusdemo 1.0.0-SNAPSHOT native (powered by Quarkus 3.6.0) started in 0.032s. Listening on: http://0.0.0.0:8080
2023-12-05 09:35:36,584 INFO  [io.quarkus] (main) Profile prod activated. 
2023-12-05 09:35:36,584 INFO  [io.quarkus] (main) Installed features: [agroal, cdi, jdbc-postgresql, mybatis, narayana-jta, resteasy-reactive, smallrye-context-propagation, vertx]

早!0.032 秒で起動しています。

しかし、起動したのは良いのですが、GETリクエストを送ってみると以下のエラーが出ました。

2023-12-05 09:36:09,127 ERROR [io.qua.ver.htt.run.QuarkusErrorHandler] (executor-thread-1) HTTP Request to /hello/1 failed, error id: 330b3903-f8a9-4b45-bb50-976396bdaed2-1: org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: org.apache.ibatis.executor.ExecutorException: No constructor found in jp.co.persolcareer.entity.Message matching [java.lang.Integer, java.lang.String]
### The error may exist in jp/co/persolcareer/mapper/MessageMapper.java (best guess)
### The error may involve jp.co.persolcareer.mapper.MessageMapper.getMessageById
### The error occurred while handling results
### SQL: SELECT * FROM message WHERE id = ?
### Cause: org.apache.ibatis.executor.ExecutorException: No constructor found in jp.co.persolcareer.entity.Message matching [java.lang.Integer, java.lang.String]
    at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:156)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:142)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:75)
    at java.base@21.0.1/java.lang.reflect.Method.invoke(Method.java:580)
    at io.quarkiverse.mybatis.runtime.TransactionalSqlSession$TransactionalSqlSessionInterceptor.invoke(TransactionalSqlSession.java:197)

MyBatisの処理中で、Messageクラスのコンストラクタが無い、と言われてますね。
レコードクラスを定義しているのでコンストラクタは存在するはずですが、Messageクラスがネイティブ実行可能ファイルに含まれていないようです。

というのもGraalVM では コンパイル時にすべてのクラスを特定し、それらをネイティブイメージに含めるのですが、リフレクションなどを使用して動的に読み込まれるようなクラスはネイティブイメージに含まれません。
MyBatis は実行時にリフレクションを使って mapper のメソッドが返却する型を判断しています。
リフレクションを使うということは、動的にクラスを読み込んでいるわけです。

では、どうするか?

リフレクションを使うことを明示的設定してあげることで、リフレクションで利用するクラスについてもネイティブイメージに含めることができます!
MessageMapper.java に@RegisterForReflectionを設定します。

@Mapper
@RegisterForReflection(classNames = {"jp.co.persolcareer.entity.Message"})
public interface MessageMapper {

    @Select("SELECT * FROM message WHERE id = #{id}")
    Message getMessageById(Integer id);

これでビルド、実行してみると正常に動きました!

~ $ curl -w "\n" http://localhost:8080/hello/1
Good Night.

Quarkus はネイティブビルドを全面的にサポートしようとしているので、基本的には設定不要でネイティブビルドができますが、まだサポートされていないライブラリもありそうです。
MyBatis についてはこの辺りで少し言及されていました。

Spring Boot でネイティブ実行可能ファイルを生成する

冒頭で Quarkus と Spring Boot との起動時間を比較したと思いますが、 Quarkus はネイティブ実行可能ファイルを実行し、Spring Boot は JVM 上で実行していました。

しかし、Spring Boot 3.0 からネイティブ実行可能ファイルの生成がサポートされているので、Spring Boot のネイティブビルド実行とも比較してみます。

Quarkus アプリケーションと同様に MyBatis でデータベースにアクセスする処理まで記載した Spring プロジェクトを用意しました。
そこから、build.gradle にネイティブイメージビルドするための依存関係を以下の通り記載していきます。

build.gradle

plugins {
    id 'org.graalvm.buildtools.native' version '0.9.28'
        ...
}
repositories {
    maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
         ...
}
dependencies {
    compileOnly 'org.mybatis.spring.native:mybatis-spring-native-core:0.1.0-SNAPSHOT'
         ...
}

また、MyBatis を使用する場合はネイティブイメージビルド用の設定クラスMyBatisNativeConfiguration.javaを作成しなければならないようです。
こちらに書かれています。

では、ネイティブコンパイルします。

~/sandbox/springdemo $ ./gradlew nativeCompile

そして実行します。

~/sandbox/springdemo $ ./build/native/nativeCompile/springdemo

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v3.1.0-SNAPSHOT)

(省略)
2023-12-05T09:40:18.434+09:00  INFO 7066 --- [           main] c.e.springdemo.SpringdemoApplication     : Started SpringdemoApplication in 0.067 seconds (process running for 0.076)

0.067 秒...。早いな...。

5 回試行の平均を Quarkus のネイティブ実行と比較してみます。

Application StartUp Time
Quarkus (Native Image) 0.023s
Spring Boot (Native Image) 0.072s

Quarkus の方が早いけど、Spring Boot も十分早いですね〜。

Quarkus のエコシステム

Spring は多くのプロジェクトから構成されていて、エコシステムがかなり充実している印象ですが、 Quarkus も RedHat によってサポートされていることと、500 以上のエクステンションがサポートされています。Quarkus のプラットフォーム
今回は触り程度なのでどこまでの機能がサポートされているのか分かりませんが、セキュリティ、キャッシュあたりも触ってみたいです。

ただ、GitHub のスター数を比較してみると、Spring Boot の伸びの方が大きいですね。

star history https://star-history.com/#quarkusio/quarkus&spring-projects/spring-boot&Date

新しい技術をベースにして他のフレームワークが出てきても、Spring は早い対応で新しい技術をサポートしている印象がありますね。
特に昨今はコンテナやサーバレスアーキテクチャのサポートが活発化しているような。
JVM の高速な起動を目指す CRaC をサポートする方針も打ち出しています。JVM Checkpoint Restore

まとめ

Quarkusについて調査しつつ、データを登録・取得するAPIをQuarkusを使用して実装してみました。 Quarkus にも今後注目していきたいところですが、Spring Boot も昨今のコンテナ環境をかなり意識して起動時間やフットプリントの最適化に力を入れている印象を受けたので、
フレームワーク選定となると、まだまだ Spring Boot が選ばれるのかなぁと思いました。

最後に

dodaX のエンジニアリングチームでは一緒に働く仲間を募集しています!
今回の記事はバックエンドの記事となりましたが、バックエンドだけでなく、フロントエンドエンジニア、インフラエンジニア、アーキテクトも絶賛募集中です! 興味ある方はぜひ採用サイトからご応募ください!

上池 哲平 Teppei Kamiike

doda_Xプロダクト統括 doda_Xエンジニアリンググループ エンジニア

2023年8月にパーソルキャリアに中途入社。前職では独立系SIerでWebシステムの開発に従事。現在はdodaXのバックエンドエンジニアとして、プロダクトの改善に取り組んでいる。

※2023年12月現在の情報です。