Quarkusプロジェクトのレイヤー構造を完全理解!API・Service・Repositoryを初心者向けにやさしく解説
生徒
「Quarkusでアプリを作り始めたんですが、APIとかServiceとかRepositoryとか、フォルダをどう分ければいいのか分かりません…」
先生
「Quarkusでも、JavaのWebアプリではレイヤー構造を意識するのが基本になります。」
生徒
「とりあえず全部同じ場所に書くのは良くないですよね?」
先生
「役割ごとに分けることで、コードが読みやすくなり、保守もしやすくなります。順番に見ていきましょう。」
1. Quarkusにおけるレイヤー構造とは
Quarkusにおけるレイヤー構造とは、「役割ごとにクラスの置き場所を分けて整理する考え方」のことです。アプリの中身を一か所にまとめて書くのではなく、仕事の内容ごとに担当を分けるイメージを持つと理解しやすくなります。
具体的には、画面や外部からの呼び出しを受け取るAPI層、処理の流れや判断を行うService層、データを扱うRepository層の三つに分ける構成がよく使われます。これにより、「どこに何を書くコードなのか」が自然と分かるようになります。
プログラミング未経験者でも、この三層構造を最初に意識しておくことで、コードがごちゃごちゃになりにくく、後から読み返したときも理解しやすくなります。Quarkusを学ぶ最初の一歩として、とても重要な考え方です。
package org.example;
/*
アプリ全体を「役割」で分けるイメージ例
・api :外からのリクエストを受け取る
・service :やりたい処理をまとめる
・repository :データを扱う
*/
public class LayerImageExample {
// このクラス自体に処理は書かず、
// 役割ごとに別のクラスへ分けていく
}
2. APIレイヤーの役割
APIレイヤーは、アプリケーションの「受付窓口」のような存在です。Webブラウザやスマートフォン、他のシステムから送られてきたリクエスト(お願い)を最初に受け取り、結果をレスポンスとして返します。Quarkusでは、このAPIレイヤーをREST APIとして実装するのが一般的で、HTTP通信を担当します。
ここで重要なのは、APIレイヤーではできるだけシンプルな処理だけを行うという考え方です。例えば、URLやHTTPメソッドを受け取り、必要な情報をServiceレイヤーに渡すまでが役割です。計算や業務ロジックなどの複雑な処理は行わず、専門のServiceレイヤーに任せます。
APIレイヤーを薄く保つことで、「どこで何をしているのか」が分かりやすくなり、プログラミング初心者でもコードの流れを理解しやすくなります。また、修正やテストもしやすくなり、実務でもよく使われる設計になります。
まずは「APIレイヤー=リクエストを受けて、Serviceに処理をお願いし、結果を返す場所」と覚えておくとよいでしょう。
package org.example.api;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.example.service.HelloService;
@Path("/hello")
public class HelloResource {
@Inject
HelloService helloService;
@GET
public String hello() {
// Serviceレイヤーに処理をお願いして、結果をそのまま返す
return helloService.message();
}
}
このサンプルでは、「/hello」というURLにアクセスすると、HelloResourceクラスが呼び出されます。@GETは「GETメソッドでアクセスされたときに実行される」という意味です。
中身を見ると、APIレイヤー自身では難しい処理はしていません。HelloServiceのmessage()メソッドを呼び出し、その戻り値をそのまま返しています。これが、APIレイヤーをシンプルに保つ基本的な書き方です。
プログラミング未経験の方は、「APIレイヤーは指示を受けて、担当者(Service)につなぐ係」とイメージすると理解しやすいでしょう。
3. Serviceレイヤーの役割
Serviceレイヤーは、アプリケーションの中心となる処理を担当します。ビジネスロジックや処理の流れをまとめる場所です。
APIレイヤーから呼び出され、必要に応じてRepositoryレイヤーを利用します。複数の処理を組み合わせるのも、このServiceレイヤーの役割です。
Serviceに処理を集約することで、APIとデータアクセスを分離でき、コードの再利用性も高まります。
package org.example.service;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class HelloService {
public String message() {
return "Hello Quarkus Layered Architecture";
}
}
4. Repositoryレイヤーの役割
Repositoryレイヤーは、データベースとのやり取りを担当します。データの取得、保存、更新、削除といった処理をまとめる場所です。
Quarkusでは、Hibernate ORMやPanacheなどを利用してRepositoryを実装することが一般的です。Serviceレイヤーから呼び出されることで、データ操作を行います。
データアクセス処理をRepositoryに集約することで、ServiceやAPIがデータベースの詳細を意識せずに済みます。
package org.example.repository;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class SampleRepository {
public String findMessage() {
return "Database Message";
}
}
5. レイヤー構造を分けるメリット
Quarkusプロジェクトでレイヤー構造を採用する最大のメリットは、責任が明確になることです。どこに何を書くべきか迷いにくくなります。
また、修正や機能追加の影響範囲が限定されるため、保守性が向上します。初心者でも、大きなプロジェクト構成を理解しやすくなります。
API、Service、Repositoryを分ける考え方は、他のJavaフレームワークでも共通して使える知識です。
6. よくある初心者のつまずきポイント
初心者がよく悩むのは、「どこまでをServiceに書くか」「APIに処理を書いてしまって良いのか」という点です。
基本的には、APIは受け口、Serviceは処理、Repositoryはデータ操作と役割を固定すると迷いにくくなります。
最初は多少厳密でなくても構いませんが、役割を意識して書く習慣をつけることが大切です。
7. Quarkusとレイヤー構造の相性
QuarkusはCDIによる依存性注入が標準で使えるため、レイヤー構造との相性が非常に良いです。
ServiceやRepositoryを注入するだけで、自然な形で層を分離できます。設定も少なく、コードもシンプルに保てます。
この設計を理解しておくことで、Quarkus開発の基礎体力が身につきます。
まとめ
今回の記事では、Quarkusにおけるレイヤー構造の重要性と、API・Service・Repositoryという三層構造の具体的な役割について深く掘り下げてきました。JavaのモダンなフレームワークであるQuarkusにおいて、なぜこのように役割を分ける必要があるのか、その理由は単なる慣習ではなく、アプリケーションの拡張性やテストのしやすさに直結するからです。
Quarkus開発で押さえておきたい設計のポイント
改めて整理すると、各レイヤーには以下のような責任があります。
- API層(Resource):外部インターフェース。HTTPリクエストの検証やステータスコードの制御に専念します。
- Service層:ビジネスロジックの心臓部。複数のエンティティを操作したり、外部連携を行ったりするロジックをここに集約します。
- Repository層:データアクセスの抽象化。Panacheなどを用いてデータベース操作をカプセル化し、上位層がDBの詳細を知らなくても済むようにします。
これらの階層を意識することで、スパゲッティコード(何でもどこにでも書かれている状態)を防ぎ、チーム開発においても「どこを直せばいいか」が即座に判断できるようになります。QuarkusのDI(依存性注入)機能を活用すれば、これらの層を疎結合に保つことは非常に簡単です。
実践的なサンプルプログラム:ユーザー登録機能の例
ここでは、学んだ三層構造をさらに具体化するために、ユーザー情報を保存するシミュレーションプログラムを紹介します。
1. Repositoryレイヤー(データアクセス)
まずは、ユーザーデータを管理するリポジトリです。ここでは簡単なリスト形式でデータを保持する例を示します。
package org.example.repository;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.ArrayList;
import java.util.List;
@ApplicationScoped
public class UserRepository {
private List<String> users = new ArrayList<>();
public void save(String username) {
users.add(username);
System.out.println("DB保存完了: " + username);
}
public List<String> findAll() {
return users;
}
}
2. Serviceレイヤー(ビジネスロジック)
次に、ビジネスロジックを扱うサービス層です。重複チェックなどのバリデーションをここで行います。
package org.example.service;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.example.repository.UserRepository;
import java.util.List;
@ApplicationScoped
public class UserService {
@Inject
UserRepository userRepository;
public String registerUser(String name) {
if (name == null || name.isEmpty()) {
return "名前が無効です";
}
userRepository.save(name);
return "登録成功: " + name;
}
public List<String> getUsers() {
return userRepository.findAll();
}
}
3. APIレイヤー(リソース)
最後に、エンドユーザーとの窓口となるAPI層です。
package org.example.api;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.QueryParam;
import org.example.service.UserService;
@Path("/users")
public class UserResource {
@Inject
UserService userService;
@GET
@Path("/add")
public String add(@QueryParam("name") String name) {
return userService.registerUser(name);
}
@GET
public String list() {
return "登録ユーザー一覧: " + userService.getUsers().toString();
}
}
4. 実行結果の確認
実際にAPIを叩いた際の、コンソール出力やレスポンスのイメージは以下の通りです。
(ブラウザで /users/add?name=Quarkus-kun にアクセスした場合)
レスポンス: 登録成功: Quarkus-kun
DB保存完了: Quarkus-kun
(続けて /users にアクセスした場合)
レスポンス: 登録ユーザー一覧: [Quarkus-kun]
このように、役割が綺麗に分かれていることがわかりますね。APIが「何を受け取るか」を決め、Serviceが「どう処理するか」を考え、Repositoryが「どう保存するか」を実行する。この黄金律を守ることが、Quarkusエンジニアとしての第一歩です。
生徒
「先生、今回の解説で三層構造のイメージがかなり具体的に湧いてきました!APIは受付嬢、Serviceは料理人、Repositoryは倉庫番、みたいな感じでしょうか?」
先生
「おお、その比喩は非常に分かりやすいですね。受付(API)が直接倉庫(DB)に走り出したらパニックになりますから、ちゃんと料理人(Service)にオーダーを出すのが正しい流れです。」
生徒
「これって、規模が小さいアプリでも絶対に分けないといけないんですか?」
先生
「良い質問です。極めて小規模なプロトタイプなら1つのクラスにまとめても動きます。でも、後から機能を追加したくなった時に、結局層を分けることになります。最初からこのテンプレートに沿って書く癖をつけておくと、後々の自分が楽になりますよ。」
生徒
「確かに、後からぐちゃぐちゃのコードを整理するのは大変そうですね…。あと、Quarkusだと@Injectを使うだけで簡単に他の層を呼び出せるのが便利だと感じました。」
先生
「そうですね。Quarkusの強力なDI機能があるからこそ、こうした設計がスマートに実装できるんです。レイヤー間の依存を疎結合に保つことで、テストコードも書きやすくなります。次は、各層の単体テストの書き方についても学んでいきましょうか。」
生徒
「ぜひお願いします!まずはこの三層構造を意識して、自分でも簡単なCRUDアプリを作ってみようと思います!」