🆎

リクルート新人研修資料詳説 Part1 ~データモデルとドメインモデル~

に公開

概要

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

  • Webアプリケーションで使われるモデルにはデータモデルとドメインモデルの2種類ある
  • データモデルはデータだけ、ドメインモデルはデータとその処理ロジックを持つ
  • モデル毎に開発手法が異なる

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

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

読了目安:15分

前回の記事:https://zenn.dev/izumi_ren/articles/075e2e0ad2724a
次回の記事:https://zenn.dev/izumi_ren/articles/3cdf7030892e97

データモデルとドメインモデル

Webアプリケーションで用いられるモデルは2種類に大別されます。
データモデルとドメインモデルです。

データモデル

データモデルは、単にデータだけを入れたオブジェクトで、

  • データベースのテーブルに対応するエンティティ
  • DTO
    等が該当します。

データモデルの例:

@Getter
@Setter
public class UserDto {
    private String familyName;
    private String lastName;
}

このモデルの特徴は、そのモデルの振る舞いが規定されておらず、ただデータだけを持っているという点です。

プレゼンテーション層(Controller、View等)やアプリケーション層(Service等)、データアクセス層(DAO等)でそれらのモデルが持っているデータを加工/表示/永続化することで、ユーザーの行いたい操作を実現します。

例えば、先ほどのUserDtoからフルネームを取得する場合、プレゼンテーション層かアプリケーション層のどこかに以下のようなメソッドを書くことになります。

public String getFullName(UserDto userDto) {
    String familyName = userDto.getFamilyName();
    String lastName = userDto.getLastName();

    if(familyName == null || familyName.length() == 0){
        throw new IllegalArgumentException("familyName is invalid");
    }
    if(lastName == null || lastName.length() == 0){
        throw new IllegalArgumentException("lastName is invalid");
    }
    return familyName + " " + lastName;
}

上記のように、データモデルはデータとそれを処理するロジックが別の箇所に書かれることになるので、

  • 重複したロジックが書かれやすい
  • ロジックの変更が手間
    といった特徴があります。

ドメインモデル

ドメインモデルは、データとそれを処理するロジックの両方が入ったオブジェクトで、
以下のようなものを指します。

ドメインモデルの例:

public class User {
    private String familyName;
    private String lastName;

    public User(String familyName, String lastName) {
        if (!isValidFamilyName(familyName)) {
            throw new IllegalArgumentException("familyName is invalid");
        }
        if (!isValidLastName(lastName)) {
            throw new IllegalArgumentException("lastName is invalid");
        }
        this.familyName = familyName;
        this.lastName = lastName;
    }

    private boolean isValidFamilyName(String familyName) {
        return familyName != null && familyName.length() > 0;
    }

    private boolean isValidLastName(String lastName) {
        return lastName != null && lastName.length() > 0;
    }

    public String getFullName(){
        return familyName + " " + lastName;
    }
}

このモデルの特徴は、「事業目的を達成するための知識やルールを表現している」という点です。

上記のUserの例では、

  • 姓名ともに0文字やnullはあり得ない
  • 姓と名を、間に半角空白を挟んで足したものを「フルネーム」とする
    という知識・ルールをメソッドで表現しています。
    Userからフルネームを取得するには、ただgetFullName()を呼び出せばよいです。

ドメインモデルには、データとそれを処理するロジックが同一の箇所に書かれるため、

  • 重複したロジックが書かれにくい
  • ロジックの変更が簡単
    といった特徴があります。

モデル別の設計手法

使用するモデルによって、設計手法は異なります。
データモデルに対応するものとして、トランザクションスクリプト
ドメインモデルに対応するものとして、ドメイン駆動設計があります。

トランザクションスクリプト

トランザクションスクリプトとは業務手順をそのままコードに起こしてそれをプレゼンテーション層(Controller、View等)やアプリケーション層(Service等)に記述したものであり、
具体的には以下の流れになります。

① プレゼンテーション層(Controller)からInputを受け取る
② Inputされた値を検証・計算
③ データベース保存・参照
④ 場合によって他のシステムを呼び出す

あれやって、これやって、次はこうして…
といった感じでやることを全て書き連ねるという点で「手続き的」と言えます。
参考:https://freelance.shiftinc.jp/column/procedural-linguistics/

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

ドメイン駆動設計

ドメイン駆動設計はドメインモデルを組み合わせて業務手順を実行するもので、プレゼンテーション層やアプリケーション層はあくまでオーケストレーション(ドメインモデルの処理を使うだけ)のみを行います。

オブジェクト間で行って欲しい処理を送受信することで処理を実行する、
という点で「オブジェクト指向」の考え方に従った開発だと言えます。
参考:https://e-words.jp/w/メッセージパッシング.html

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

補足:二つのエンティティ

エンティティは複数の意味で使われるややこしい言葉です。
その中でもトランザクションスクリプトとドメイン駆動設計に関わる二つの意味を紹介します。

データベースのエンティティ

まず一つ目は、
「データベースやデータモデル内で管理・識別される対象(オブジェクト)」
という意味です。わかりやすく言えば、データベースのテーブルに対応するオブジェクトです。
ER図の"E"にあたる概念ですね。
参考:https://wa3.i-3-i.info/word11594.html

トランザクションスクリプトにおいては、前述したデータモデルの一種として大活躍します。

データベースから参照した情報をエンティティにマッピングし、それらにユースケース毎に適切な加工を施してDTOに詰め込んで外部に返却する、といった流れはトランザクションスクリプトの基本です。

データベースのエンティティのイメージ

ドメインモデルのエンティティ

二つ目は、
「ビジネスの主要な概念やライフサイクルを表現し、一意の識別子を持つドメインモデル」
という意味です。

少々難しいのでここでは細かな説明を割愛しますが、とりあえず「ドメインモデルの一種」と思ってもらえば大丈夫です。
参考:https://qiita.com/yoron0122/items/cab5c482810018480400

このドメインモデルのエンティティは、データベースのエンティティとは対応しません。
Userエンティティのフィールドが、Userテーブルのカラムと一致するとは限らないのです。

ドメインモデルのエンティティのイメージ

トランザクションスクリプトでは「データベースのエンティティ」のみを用いて処理を記述しますが、
ドメイン駆動設計では「データベースのエンティティ」と「ドメインモデルのエンティティ」の双方を使って処理を記述します。ややこしいですね。

まとめ

  • モデルはデータモデルとドメインモデルの2種類
  • データモデルはデータのみ、ドメインモデルはデータとそれを処理するロジックの両方を持つ
  • データモデルはトランザクションスクリプト、ドメインモデルはドメイン駆動設計

次回はトランザクションスクリプトのアーキテクチャについてより掘り下げて紹介します。
最後まで読んでいただきありがとうございました。

次回:https://zenn.dev/izumi_ren/articles/3cdf7030892e97

Discussion