best-java-structure について
best-java-structureは、Spring BootとMyBatis-Plusを使うJavaチーム向けのレイヤードアーキテクチャ設計ガイドです。新規開発、リファクタリング、レビューに向けて、五層の責務や依存方向、データ転送の規則を説明します。
主な機能
- 01五層の責務を定義
- 02一方向依存とインターフェース抽象化
- 03DTO、VO、Entityの受け渡し方
- 04例外処理とトランザクション境界
- 05レイヤー間呼び出しの注意点
活用シーン
- 01新しいJava Webプロジェクトの構成設計
- 02マルチモジュールJavaシステムの改修
- 03コードレビューでの設計規約確認
- 04チームのレイヤー開発標準の策定
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ chasepassion/ skills --skill best-java-structureインストール後、Claude Code で/best-java-structureを入力して読み込まれたか確認します。
詳細
Java レイヤードアーキテクチャ設計パターン(Spring Boot + MyBatis-Plus)
Spring Boot と MyBatis-Plus を使って、従来の5層アーキテクチャを実装するための実践ガイドです。
適用する場面
次のようなシナリオで、このスキルを使用します。
- 新しい Java Web プロジェクトを作成するとき
- 既存のプロジェクトアーキテクチャをリファクタリングするとき
- マルチモジュールの Java システムを設計するとき
- コードレビューでアーキテクチャへの準拠を確認するとき
- チーム開発のアーキテクチャ標準を定めるとき
- データベースや技術スタックを移行するとき
前提条件
- Spring Boot 3.x
- データアクセスに MyBatis-Plus を使用
- API 境界で DTO と VO を分離
クイックリファレンス
| 優先度 | カテゴリ | 影響範囲 | プレフィックス |
|---|---|---|---|
| 1 | 関心の分離 | アーキテクチャの明確さ | -principle- |
| 2 | 一方向の依存関係 | 依存関係の管理 | -dependency- |
| 3 | インターフェースの抽象化 | 置き換え可能性 | -abstraction- |
| 4 | データ転送 | API の一貫性 | -data- |
| 5 | 例外処理 | エラー処理 | -exception- |
| 6 | レイヤー間の呼び出し | アーキテクチャへの準拠 | -crosslayer- |
| 7 | 逆方向の依存関係 | アーキテクチャへの準拠 | -reverse- |
| 8 | エンティティの公開 | セキュリティ | -entity-exposure- |
基本原則
1. 関心の分離(SoC)
各レイヤーは1種類の問題を解決し、他のレイヤーに属する責務を担ってはなりません。
// Correct: clear responsibilities per layer
// Presentation layer: HTTP handling only
@RestController
public class ProjectController {
private final ProjectService projectService;
public ProjectController(ProjectService projectService) {
this.projectService = projectService;
}
@GetMapping("/{id}")
public ApiReturn<ProjectVo> getProject(@PathVariable Long id) {
Project project = projectService.getProject(id);
ProjectVo vo = new ProjectVo();
BeanUtils.copyProperties(project, vo);
return ApiReturn.of(vo);
}
}
// Business logic layer: business rules only
@Service
public class ProjectService {
private final ProjectMapper projectMapper;
public ProjectService(ProjectMapper projectMapper) {
this.projectMapper = projectMapper;
}
public Project getProject(Long id) {
Project project = projectMapper.selectById(id);
if (project == null) {
throw new BusinessException(404, "Project does not exist");
}
return project;
}
}
// Data access layer: data interfaces only
public interface ProjectMapper extends BaseMapper<Project> {
}
2. 一方向の依存関係
上位レイヤーは下位レイヤーを呼び出せますが、下位レイヤーは上位レイヤーに依存してはなりません。
// Correct: depend on a lower layer abstraction
@Service
public class ProjectService {
private final ProjectMapper projectMapper;
public ProjectService(ProjectMapper projectMapper) {
this.projectMapper = projectMapper;
}
}
// Incorrect: cross-layer invocation
@RestController
public class ProjectController {
private final ProjectMapper projectMapper;
public ProjectController(ProjectMapper projectMapper) {
this.projectMapper = projectMapper;
}
}
3. インターフェースの抽象化
レイヤー間のやり取りには、具象実装ではなくインターフェースを使用します。
// Correct: mapper interface managed by MyBatis-Plus
public interface ProjectMapper extends BaseMapper<Project> {
}
// Incorrect: depending on a non-existent implementation class
public class ProjectService {
private ProjectMapperImpl projectMapperImpl;
}
アーキテクチャのレイヤー
従来の5層アーキテクチャ
Presentation Layer → Business Logic Layer → Data Access Layer → Persistence Layer → Database Layer
| レイヤー | Spring のスタック | 一般的な名称 | 主な責務 |
|---|---|---|---|
| プレゼンテーション層 | @Controller | Controller、API | HTTP リクエストとレスポンスを処理する |
| ビジネスロジック層 | @Service | Service | ビジネスルールとワークフローを実装する |
| データアクセス層 | @Mapper | Mapper、DAO | DB アクセスインターフェースを定義する |
| 永続化層 | MyBatis XML、Entity | Mapper XML、Entity | SQL マッピングとエンティティ定義 |
| データベース層 | MySQL | Database | データを保存および取得する |
レイヤーの定義
プレゼンテーション層
責務:
- HTTP リクエストとレスポンスの処理
- パラメーターの受け取りと検証
- ルーティングとディスパッチ
- データ形式の変換(DTO ↔ Entity ↔ VO)
- 例外処理と HTTP ステータスのマッピング テンプレート:
@RestController
@RequestMapping("/api/projects")
public class ProjectController {
private final ProjectService projectService;
public ProjectController(ProjectService projectService) {
this.projectService = projectService;
}
@GetMapping("/{id}")
public ApiReturn<ProjectVo> getProject(@PathVariable Long id) {
Project project = projectService.getProject(id);
ProjectVo vo = new ProjectVo();
BeanUtils.copyProperties(project, vo);
return ApiReturn.of(vo);
}
@PostMapping
public ApiReturn<ProjectVo> createProject(@Valid @RequestBody ProjectCreateRequest request) {
Project project = new Project();
BeanUtils.copyProperties(request, project);
Project created = projectService.createProject(project);
ProjectVo vo = new ProjectVo();
BeanUtils.copyProperties(created, vo);
return ApiReturn.of(vo);
}
}
設計上の注意点:
- 薄く保つ:HTTP 関連のロジックのみを扱います
- データ転送には DTO と VO を使用します
- Bean Validation とグローバル例外処理で検証を一元化します
- ビジネスロジックを含めません
- データベースに直接アクセスしません
ビジネスロジック層
責務:
- ビジネスルールとワークフローの実装
- データ検証とビジネスチェック
- 認可とアクセス制御
- トランザクション管理(境界はこのレイヤーに置きます)
- 複数のサービスやマッパーの調整
- データアクセス層の呼び出し テンプレート:
@Service
public class ProjectService {
private final ProjectMapper projectMapper;
public ProjectService(ProjectMapper projectMapper) {
this.projectMapper = projectMapper;
}
public Project getProject(Long id) {
if (id == null || id <= 0) {
throw new BusinessException(400, "Invalid project ID");
}
Project project = projectMapper.selectById(id);
if (project == null) {
throw new BusinessException(404, "Project does not exist");
}
return project;
}
@Transactional
public Project createProject(Project project) {
if (project.getCustomerId() == null) {
throw new BusinessException(400, "Customer ID must not be null");
}
project.setCreatedTime(LocalDateTime.now());
project.setStatus(ProjectStatus.CREATED);
projectMapper.insert(project);
return project;
}
}
設計上の注意点:
- コアとなるビジネスロジックを含みます
- トランザクションには
@Transactionalを使用します - ビジネス上の検証を実行します
- 複数のサービスやマッパーを統括します
- HTTP に関する処理を含めません
- DB 接続の処理を直接行いません
- Service は DTO を返してはなりません(DTO は入力モデルです)。Entity/Domain または VO を一貫して返します。
データアクセス層(MyBatis-Plus)
責務:
- DB アクセスインターフェースを定義します
- CRUD の抽象化を提供します
- 永続化の詳細をカプセル化します
- 必要に応じて集約クエリ用インターフェースを提供します テンプレート:
public interface ProjectMapper extends BaseMapper<Project> {
}
設計上の注意点:
- インターフェースのみとし、具体的な実装は含めません
- メソッド名から意図が明確に伝わるようにします
- SQL 文は記述しません(複雑な SQL には XML を使用します)
- ビジネスロジックは含めません
- トランザクション管理は行いません
- Service 層に依存してはなりません
永続化層
責務:
- SQL マッピングと結果マッピング
- エンティティの定義
- データベース固有の SQL 断片(必要な場合) テンプレート(MyBatis-Plus XML):
<mapper namespace="com.example.mapper.ProjectMapper">
<select id="selectActive" resultType="com.example.entity.Project">
SELECT * FROM project WHERE status = 'CREATED'
</select>
</mapper>
設計上の注意点:
- SQL の作成と最適化に重点を置きます
- 結果マッピングと型変換を行います
- 再利用性を高めるために SQL 断片を使用します
- ビジネスロジックは含めません
- ビジネス上の検証は行いません
- 接続管理とトランザクションの境界は Spring と DataSource が処理します
データベース層
責務:
- 永続ストレージ
- データ整合性制約
- インデックスとパフォーマンスの最適化
- トランザクションのサポート テンプレート(MySQL):
CREATE TABLE project (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
customer_id BIGINT NOT NULL,
status VARCHAR(50) DEFAULT 'CREATED'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
データ転送パターン
DTO、VO、Entity の使用
Frontend → Controller → Service → Mapper → Database
DTO Entity Entity
VO Entity
変換例
@PostMapping("/projects")
public ApiReturn<ProjectVo> createProject(@Valid @RequestBody ProjectCreateRequest request) {
Project project = new Project();
BeanUtils.copyProperties(request, project);
Project created = projectService.createProject(project);
ProjectVo vo = new ProjectVo();
BeanUtils.copyProperties(created, vo);
return ApiReturn.of(vo);
}
例外処理
一元化された例外処理
public class BusinessException extends RuntimeException {
private final int code;
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public int getCode() {
return code;
}
}
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(BusinessException.class)
public ApiReturn<Void> handleBusinessException(BusinessException e) {
return ApiReturn.error(e.getCode(), e.getMessage());
}
@ExceptionHandler(Exception.class)
public ApiReturn<Void> handleException(Exception e) {
return ApiReturn.error(500, "Internal server error");
}
}
トランザクションのガイドライン
- トランザクションの境界は Service メソッドに設定します。
- 読み取り専用クエリには
@Transactional(readOnly = true)を使用します。 - デフォルトでは RuntimeException でロールバックします。必要に応じて、チェック例外でのロールバックを文書化します。
- Controller、Mapper、XML ではトランザクションを使用しないでください。
よくある落とし穴
落とし穴 1:レイヤーをまたぐ呼び出し
// Incorrect: Controller directly calls Mapper
@RestController
public class ProjectController {
private final ProjectMapper projectMapper;
public ProjectController(ProjectMapper projectMapper) {
this.projectMapper = projectMapper;
}
public List<Project> getProjects() {
return projectMapper.selectList(null);
}
}
落とし穴 2:依存関係の逆転
// Incorrect: Service depends on Controller
@Service
public class ProjectService {
private ProjectController projectController;
}
落とし穴 3:SQL 内のビジネスロジック
<select id="getById" resultMap="BaseResultMap">
SELECT * FROM project
WHERE id = #{id}
AND is_deleted = 0
AND has_permission(#{currentUserId}, id) = 1
</select>
落とし穴 4:Controller から Entity を直接返す
// Incorrect: Controller returns Entity directly
@GetMapping("/projects/{id}")
public Project getProject(@PathVariable Long id) {
return projectService.getProject(id);
}
プロジェクト構成テンプレート(推奨)
src/main/java/com/xdyai/backend/
controller/
internal/
request/
service/
common/
context/
integration/
mapper/
entity/
enumeration/
src/main/resources/
application.yml
application-dev.yml
mapper/
db/
validation/
integration/
ベストプラクティスのまとめ
プレゼンテーション層
- 薄く保ちます:HTTP ロジックのみを扱います
- データ転送には DTO/VO を使用します
- 例外処理を一元化します
- ビジネスロジックは含めません
- Mapper に直接アクセスしません
ビジネスロジック層
- 中核となるビジネスロジックを含みます
- トランザクション管理には
@Transactionalを使用します - 複数のサービスを調整します
- HTTP に関する処理は行いません
- データベース接続を直接管理しません
データアクセス層
- インターフェースのみとします
- メソッド名を明確にします
- MyBatis-Plus を介して CRUD を行います
- SQL 文は記述しません
- ビジネスロジックは含めません
永続化層
- SQL の作成に重点を置きます
- SQL 断片を使用します
- 複雑なクエリには動的 SQL を使用します
- ビジネスロジックは含めません
- ビジネス上の検証は行いません
レイヤー間の通信
- Controller ← DTO
- Service ← Entity
- Mapper ← Entity
- Controller ← VO
- レイヤーをまたぐ呼び出しを避けます
- 依存関係の逆転を避けます
コメント
Markdown 対応