C
Spring Boot/階層構造/Lesson 07

Layered Architecture — *責務をレイヤーで分ける*

30分·theory

Layered Architecture — *責務をレイヤーで分ける*

🎯 このレッスンを読んだら

このレッスンをすべて読み終えると、以下の3つを自信を持って実践できるようになります。

  • ✅ Controller → Service → Repository の3層における責務の分離
  • @Transactional を適用すべき正確な位置
  • ✅ DTO ↔ Entity 変換の責務を誰が持つべきか

学習目標をチェックリストとしてすべて答えられるようになったら、レッスンを閉じてください。

なぜ *3層* に分けるのか

核心を一言で

Spring バックエンドは通常、Controller → Service → Repository の3層にコードを分けます。各層に明確な責務を持たせることで、保守性の高いコードを実現する標準的なアーキテクチャです。

各層の役割

code
┌─────────────────────────────────────┐
│         @RestController              │  ← HTTP受信・検証・DTO変換
│  - @GetMapping·@PostMapping          │
│  - リクエストパラメータの検証          │
│  - DTO → Service 入力変換            │
└──────────────┬──────────────────────┘
               │ Service 呼び出し
               ▼
┌─────────────────────────────────────┐
│           @Service                   │  ← ビジネスロジック
│  - トランザクション (@Transactional)  │
│  - 複数 Repository の組み合わせ       │
│  - 外部 API 呼び出し(メール・決済)   │
└──────────────┬──────────────────────┘
               │ Repository 呼び出し
               ▼
┌─────────────────────────────────────┐
│         @Repository                  │  ← DB アクセス
│  - JpaRepository·QueryDSL            │
│  - シンプルな CRUD またはクエリ        │
│  - DB 以外のことは一切知らない        │
└─────────────────────────────────────┘

Controller: HTTP の出入り口。リクエストの受信・検証・レスポンス生成。ビジネスロジックは持たない。

Service: ビジネスロジックの中心。トランザクション単位。複数の Repository や外部 API を組み合わせる。

Repository: DB との対話。クエリのみ。ビジネス判断はしない。

なぜこのように分けるのか

1. 責務の分離: 各層が一つのことだけをうまく行います。Controller が DB クエリも担うと、混乱した設計になります。

2. テスト容易性: Service をテストする際、実際の Controller や Repository なしに単体テストが可能。モックに差し替えられます。

3. 変更の隔離: HTTP が GraphQL に変わっても、修正するのはController だけ。Service と Repository はそのまま。

4. トランザクション境界: @TransactionalService メソッドにのみ付けるのが標準。Controller や Repository に付けると曖昧な境界になります。

実践例

java
// === Controller ===
@RestController
@RequestMapping("/api/users")
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @PostMapping
    public UserDto create(@RequestBody @Valid CreateUserDto dto) {
        return userService.create(dto);
    }
}

// === Service ===
@Service
@Transactional
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepo;
    private final EmailService emailService;

    public UserDto create(CreateUserDto dto) {
        // ビジネスルール
        if (userRepo.existsByEmail(dto.email())) {
            throw new DuplicateEmailException();
        }
        // 保存
        User saved = userRepo.save(User.from(dto));
        // 副作用
        emailService.sendWelcome(saved.getEmail());
        // DTO 変換
        return UserDto.from(saved);
    }
}

// === Repository ===
public interface UserRepository extends JpaRepository<User, Long> {
    boolean existsByEmail(String email);
}

Controller は薄く、Service はビジネスルールで厚く、Repository はDB アクセスのみ — すっきりした構造です。

DTO — 層間のデータ運搬オブジェクト

各層は同じオブジェクトを直接やり取りしません。DTO (Data Transfer Object) に変換して受け渡します。

  • CreateUserDto — リクエストボディ
  • User — JPA エンティティ(DB マッピング)
  • UserDto — レスポンスオブジェクト

なぜ分けるのか?

  • エンティティの内部構造が外部に露出しない(セキュリティ・カプセル化)
  • パスワードのようなセンシティブなフィールドをレスポンスから自然に除外できる
  • API の変更がエンティティに影響しない(疎結合)

Java 14+ の record は DTO 作成に最適です:

java
public record CreateUserDto(@NotBlank @Email String email, @NotBlank String name) {}
public record UserDto(Long id, String email, String name) {
    public static UserDto from(User u) {
        return new UserDto(u.getId(), u.getEmail(), u.getName());
    }
}

よくあるアンチパターン

1. Controller がビジネスロジックを持つ: トランザクションが壊れ、再利用できない
2. Service が HttpServletRequest を受け取る: HTTP 依存となりテストと再利用が困難
3. Repository にビジネスルール: 同じクエリを別の場所にも書くことになる
4. エンティティをそのままレスポンスに返す: パスワード漏洩・内部構造が外部と結合

まとめ

3層アーキテクチャはSpring の標準です。各層が一つのことだけをうまく行えるよう責務を分けます。DTO で層間のデータ運搬を行うことで結合度が下がります。

🤖 AI にこう頼んでみましょう

このレッスンの概念を知っていれば、AI に具体的に指示できます。漠然とした「直して」ではなく、語彙を持ったリクエスト — それがトークン節約の出発点です。

  • 「この Spring Boot コードに Layered Architecture — 責務をレイヤーで分ける パターンを適用して」
  • 「Layered Architecture — 責務をレイヤーで分ける に関連した @SpringBootTest 統合テストを書いて」
  • 「実務で Layered Architecture — 責務をレイヤーで分ける を使う際に注意すべき落とし穴を3つ教えて」

なぜトークンが節約できるのか

概念を知らないと、AI の回答を受け取っても「それって何ですか?」と再度聞かなければなりません。その「再質問」がトークンを消費します。概念を一度学んでおけば、会話が一度で終わります。

Layered Architecture — 責任を層で - Spring Boot