🐥

リクルート新人研修資料詳説 Part2 ~トランザクションスクリプトのアーキテクチャ~

に公開

概要

この記事を読むと以下がわかります。

  • トランザクションスクリプトの構造
  • その構造の利点

なお、この記事はリクルートの 2025 年度版新人研修資料を再構成し解説する連載記事の Part2 です。
まず軽く下記の資料に目を通していただけると、より深く内容を理解できます。

今回解説する資料:
https://speakerdeck.com/recruitengineers/shi-jian-apurikesiyonshe-ji-toranzakusiyonsukuriputohenodui-ying

読了目安:15 分

前回の記事:https://zenn.dev/izumi_ren/articles/f5d5dd3310ba20
次回の記事:https://zenn.dev/izumi_ren/articles/dac6c7b7d50932

前回のおさらい

モデルにはデータモデルドメインモデルがあります。

データモデルは単にデータだけを持つモデルで、
ドメインモデルはデータとその振る舞い(データを処理するメソッド)を持っています。

そして、各モデルで開発手法が異なります。

データモデルでは、データとその処理を別々に扱う「トランザクションスクリプト」が採用されます。

トランザクションスクリプトのイメージ

ドメインモデルでは、データとその処理を同じオブジェクトで扱い、そのオブジェクトの組み合わせで処理を実現する「ドメイン駆動設計」が採用されます。

ドメイン駆動設計のイメージ

トランザクションスクリプトのアーキテクチャ

それでは、トランザクションスクリプトがどのような構造になっているのかを解説していきます。

全体像

トランザクションスクリプトは以下のように三層に分かれた構造になっています。


それぞれの層の役割について、以下で詳しく解説します。

プレゼンテーション層

外部(クライアント)との入出力を管理する層。
具体的には以下を行います。

  1. リクエストを受け取る
  2. リクエストのバリデーション
  3. アプリケーション層の呼び出し
  4. レスポンス返却

プレゼンテーション層の主な構成要素は二つです。

コントローラー(Controller)
入力(HTTP リクエスト等)の受け取り、バリデーション、アプリケーション層の呼び出し、レスポンス返却を全てやる。プレゼンテーション層とはすなわちコントローラーのこと、と考えても過言ではない。

ビュー(View)
HTML 等、実際にユーザーに表示される画面のこと。コントローラーが返すレスポンスの一種。

例:

// ユーザー検索のコントローラー
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/users/{id}")
    public ResponseEntity<UserResponse> getUserById(@PathVariable Long id) {
        // 1. リクエストのバリデーション
        if (id == null || id <= 0) {
            return ResponseEntity.badRequest().body(new UserResponse("Invalid user ID"));
        }

        // 2. アプリケーション層の呼び出し
        User user = userService.findUserById(id);

        // 3. レスポンス返却
        return ResponseEntity.ok(new UserResponse(user.getId(), user.getName()));
    }
}

アプリケーション層

システムの中核である、ビジネスロジックを実装する層。
プレゼンテーション層から受け取った情報を処理し、必要に応じてデータアクセス層を呼び出しデータの操作を行います。

構成要素は以下の二つです。

モデル
特定の概念を表すオブジェクト。トランザクションスクリプトにおいてはデータだけを持ったデータモデルを指す。具体的には、Entiy、DTO など。

サービス
上記のモデルに処理を行い、必要に応じてデータアクセス層を呼び出す。

例:

// データモデル(Entity)
@Getter
@Setter
public class User {
    private Long id;
    private String name;
    private String email;

    public User(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
}

// サービス(ビジネスロジック)
@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    public User findUserById(Long id) {
        // ビジネスルールのチェック
        if (id == null || id <= 0) {
            throw new IllegalArgumentException("ユーザーIDは1以上である必要があります");
        }

        // データアクセス層の呼び出し
        User user = userDao.findById(id);

        // ユーザーが見つからない場合の処理
        if (user == null) {
            throw new RuntimeException("ユーザーが見つかりません。ID: " + id);
        }

        return user;
    }
}

データアクセス層

データベースやファイルなどとやり取りを行う層。
アプリケーション層からデータの取得・保存先要求を受け付け、データベースに対して SQL を実行し、
結果をアプリケーション層に返します。

この層の構成要素は場合によって様々ですが、代表的なのは DAO です。
DAO とは Data Access Object の略で、テーブルにアクセスする機能を持った単一のオブジェクトのことを指します。

例:

// DAO(Data Access Object)
@Repository
public class UserDao {

    private JdbcTemplate jdbcTemplate;

    public User findById(Long id) {
        try {
            // SQL実行
            String sql = "SELECT id, name, email FROM users WHERE id = ?";
            return jdbcTemplate.queryForObject(sql, new Object[]{id},
                (rs, rowNum) -> new User(
                    rs.getLong("id"),
                    rs.getString("name"),
                    rs.getString("email")
                ));
        } catch (EmptyResultDataAccessException e) {
            // データが見つからない場合はnullを返す
            return null;
        }
    }

    public List<User> findAll() {
        String sql = "SELECT id, name, email FROM users";
        return jdbcTemplate.query(sql,
            (rs, rowNum) -> new User(
                rs.getLong("id"),
                rs.getString("name"),
                rs.getString("email")
            ));
    }
}

アーキテクチャの利点

なぜわざわざ三層にわかれているのでしょうか。
Controller にビジネスロジックもデータベースのアクセスも全て書いてしまって良いように思えます。
なぜそうしないのかを以下で解説します。

修正範囲を限定できる

各層が異なる責任を持つことで、修正の影響が出る範囲を特定しやすいです。

  • プレゼンテーション層: 「どう見せるか」「どう受け取るか」だけ
  • アプリケーション層: 「何をするか」(ビジネスロジック)だけ
  • データアクセス層: 「どう保存するか」だけ

このように各層が分離しているので、例えば「画面のデザインを変更したい」場合はプレゼンテーション層だけを修正すれば良く、ビジネスロジックやデータベースアクセスの部分は一切触る必要がありません。

例:もしすべてをコントローラーに書いた場合

@GetMapping("/users/{id}")
public ResponseEntity getUserById(@PathVariable Long id) {
    // バリデーション
    if (id == null || id <= 0) return badRequest();

    // ビジネスロジック
    if (id > 999999) throw new Exception("IDが大きすぎます");

    // データベースアクセス
    String sql = "SELECT id, name, email FROM users WHERE id = ?";
    PreparedStatement ps = connection.prepareStatement(sql);
    ps.setLong(1, id);
    ResultSet rs = ps.executeQuery();
    if (!rs.next()) throw new Exception("ユーザーが見つかりません");

    return ok(new UserResponse(rs.getLong("id"), rs.getString("name")));
}

このコードをみると、バリデーション、ビジネスロジック、データベースアクセスがすべて混在していて、どこに何が書かれているのか分かりにくく、修正時に影響範囲を把握するのが困難なのがわかると思います。

テストがしやすい

三層に分離することで、各層を独立してテストできるようになります。

  • プレゼンテーション層: リクエスト/レスポンスの形式をテスト
  • アプリケーション層: ビジネスロジックをテスト(データベースなしでも可能)
  • データアクセス層: データベースアクセスのみをテスト

特にアプリケーション層は、データアクセス層をモック化することで、データベースに依存せずに高速にテストできます。

再利用性が高い

各層が独立していることで、コードの再利用が可能になります。
例えば、同じサービス(アプリケーション層)のメソッドを、
異なる複数のController(プレゼンテーション層)から呼ぶことができます。

まとめ

  • トランザクションスクリプトはプレゼンテーション層・アプリケーション層・データアクセス層の三層構造
  • 各層が異なる責任を持つことで、修正範囲が限定されるなどのメリットがある

次回はトランザクションスクリプトの長所・短所について解説します。
最後まで読んでいただきありがとうございました。

次回:https://zenn.dev/izumi_ren/articles/dac6c7b7d50932

Discussion