🏗️ Spring Framework 講座【第18回】ビジネスの心臓部!〜三層構造の連携とService層の役割〜

docs

前回は、ユーザーからの入力を守るためのバリデーションと、その際のエラーを適切に処理するエラーハンドリングについて学びました。これで、アプリケーションの「入り口」は強固になりましたね。

今回は、Webアプリケーション設計の基本である三層構造(Controller、Service、Repository)を改めて見直し、特に**Service層(ビジネスロジック層)**が果たす役割と、DI(依存性の注入)を使った層間の連携方法を具体的に学びます。

1. 三層構造の再確認と責務(責任)の分離

業務システム開発では、機能ごとに責任範囲を明確にする「責務の分離」が非常に重要です。Springの三層構造は、この責務を明確にするための設計パターンです。

層の名称責務(役割)Springのアノテーション
Controller(コントローラ層)外部との通信(リクエスト/レスポンス)の処理@RestController
Service(サービス層)業務ロジックの実行、複数のRepositoryの制御@Service
Repository(リポジトリ層)データベースとのデータのやり取り@Repository

なぜ分離するのか?

例えば、ユーザー認証(Service層の仕事)の方法が変わっても、リクエスト受付(Controller層の仕事)には影響を与えないようにするためです。これにより、コードの変更耐性が上がり、テストもしやすくなります。


2. Service層の重要な役割

Service層は、ControllerとRepositoryの「間に立つ存在」であり、ビジネスルールの中心です。

Service層の主な役割は以下の通りです。

2-1. 業務ロジックの実装

「商品を購入したらポイントを付与する」「在庫がマイナスにならないかチェックする」など、**システム固有のルール(業務ルール)**を実装します。

2-2. 複数のデータ操作の制御(トランザクション管理)

一つの業務処理(例: 注文処理)で、「在庫の引き落とし」と「注文履歴の書き込み」など、複数のデータベース操作が必要な場合があります。Service層は、これらの操作を一つのトランザクション(一連の処理)としてまとめ、全て成功するか、全て失敗するかの保証(第26回で詳述)を行います。

2-3. ControllerとRepositoryの橋渡し

Controllerからの要求を受け取り、それを実現するためにどのRepositoryをどういう順番で呼び出すかを決定し、結果をControllerに戻します。


3. 三層構造の具体的な連携コード

実際に、ユーザーを登録する機能を通じて、三層構造がどのように連携するかを見ていきましょう。

3-1. Repository層:データ操作の定義

データ操作のインターフェースと、実装(ここでは省略)を定義します。

Java

// UserRepository.java
@Repository
public class UserRepository {
    
    // ユーザーをDBに保存するメソッド
    public User save(User user) {
        System.out.println("【Repository】データベースにユーザー情報を保存しました。");
        return user;
    }
}

3-2. Service層:業務ロジックの定義とDI

Service層は UserRepository に依存し、DIによってそれを注入してもらいます。

Java

// UserService.java
import lombok.RequiredArgsConstructor;

// Lombokによりコンストラクタインジェクションを自動生成(第7回)
@RequiredArgsConstructor 
@Service
public class UserService {
    
    // UserRepositoryへの依存を final で宣言
    private final UserRepository userRepository; 

    // 業務ロジックのメソッド
    public User registerNewUser(String username, String email) {
        // ① 業務ルールチェック(例: 既にユーザー名が存在しないか)
        if (username.equals("admin")) {
            throw new IllegalArgumentException("「admin」は使用できません。");
        }
        
        User newUser = new User(username, email);
        
        // ② Repositoryにデータ保存処理を委譲
        User savedUser = userRepository.save(newUser);
        
        // ③ 処理後の付加的なロジック(例: ログ出力、メール送信)
        System.out.println("【Service】ユーザー登録が完了しました。");

        return savedUser;
    }
}

3-3. Controller層:通信の制御とServiceへの委譲

Controllerは UserService に依存し、リクエストの受付後、全ての業務処理をService層に委譲します。

Java

// UserController.java
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RequiredArgsConstructor
@RestController
@RequestMapping("/users")
public class UserController {
    
    // UserServiceへの依存を final で宣言
    private final UserService userService; 

    @PostMapping("/register")
    public String registerUser(@RequestParam String username, @RequestParam String email) {
        
        try {
            // ① Service層の業務ロジックを呼び出す
            User registeredUser = userService.registerNewUser(username, email);
            
            // ② 処理結果をクライアントに返す
            return "登録成功: ID=" + registeredUser.getId();
            
        } catch (IllegalArgumentException e) {
            // Service層で発生した業務エラーをControllerで捕捉し、エラーメッセージを返す
            return "登録失敗: " + e.getMessage();
        }
    }
}

4. 適切な層への配置が重要

上記の例のように、new@Autowired の記述はService層やController層で行いますが、データベースにアクセスする処理(save メソッドの中身)はRepository層にしか書いてはいけません

この厳格な責務の分離こそが、大規模なシステムを破綻させずに開発し続けるための鍵となります。

✅ 本日のまとめ

  • 業務システムは、ControllerServiceRepository三層構造で設計され、責務が厳密に分離される。
  • Service層ビジネスロジックの中心であり、複数のRepositoryを制御し、トランザクション管理を行う重要な役割を担う。
  • 各層間の連携は、**DI(依存性の注入)**によって行われ、これにより部品同士の結合度が低く保たれる。
  • Controllerはリクエストの受付とServiceへの処理の委譲に専念する。

🔔 次回予告

これでWebアプリケーションの骨格は完成しましたが、業務システム開発には、環境ごとの設定管理が必須です。例えば、開発環境と本番環境でデータベースの接続先は変える必要があります。

次回は、Spring Bootの機能を使って、設定ファイル(application.properties)を環境ごとに切り替え、動的に管理する「プロパティ管理」と「プロファイル」の仕組みを学びます。

次回:【第19回】プロパティ管理と環境設定(application.properties/profiles) にご期待ください!

コメント

タイトルとURLをコピーしました