0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DAO・DTO・Entity・Repository・Modelの違いを整理する:誤用しやすい設計用語の意味

0
Posted at

はじめに

バックエンドのコードには、DAO、DTO、Entity、Repository、Modelといった名前が並びます。同じ単語でも、JPA、Spring、DDD、クリーンアーキテクチャでは指すものが少しずつ異なります。

この記事では、用語を暗記するのではなく、何を表し、どの境界で使い、何に依存しているかで責務を判断するための整理をします。

用語は役割と文脈を分けて読む

まず、よく使われる用語を大まかに整理します。

用語 主な役割 見分けるポイント
DTO 境界を越えてデータを運ぶ 通信や層間受け渡しの形か
Entity 同一性を持つもの、またはORMの永続化対象 DDDかJPAか
DAO データソースへのアクセスを隠す SQLや接続などを扱うか
Repository ドメインオブジェクトの取得・保存を抽象化する ドメイン側から集合のように見えるか
Model 幅広い総称 何のモデルか補足があるか
Value Object 値に意味を持たせる 値で同一性を判断するか
Mapper 表現を変換する 変換だけを担うか
Service ユースケースまたはドメイン処理をまとめる ApplicationかDomainか

この表は命名規約ではありません。同じ名前でも、プロジェクト内での責務と依存関係を確認する必要があります。

DTOはAPIレスポンスだけを指す言葉ではない

DTOはData Transfer Objectの略です。元来は、リモート呼び出しで複数の値をまとめて渡し、呼び出し回数を減らすためのパターンでした。Data Transfer Object - Martin Fowler

現在は、HTTP境界やアプリケーション層の入力・出力に使うデータ構造も、広い意味でDTOと呼ばれます。

public record UserResponse(
    Long id,
    String name,
    String email
) {}

APIのRequestやResponseは典型例ですが、外部APIの受信形式、BFFとバックエンド間の形式、バッチの入出力などにも使えます。重要なのは、業務の中心を表すことより、ある境界でデータを運ぶ形であることです。

DTOに重要な業務ルールを集め始めると、転送用データとドメインロジックの境界が曖昧になります。入力形式の検査と、価格計算や状態遷移のような業務ルールは分けて考えると追いやすくなります。

EntityはJPAとDDDで意味が違う

Entityは特に混同しやすい用語です。JPAのEntityとDDDのEntityは、焦点が異なります。

JPAのEntityは永続化対象のJavaクラスです

JPAでは、@Entityを付けたクラスが永続化の対象です。

@Entity
@Table(name = "users")
public class UserEntity {
    @Id
    private Long id;

    private String name;

    private String email;

    protected UserEntity() {
        // JPAがインスタンス生成に使う
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }
}

JPAのEntityにはpublicまたはprotectedの無引数コンストラクタが必要です。上の例はフィールドアクセスの基本形だけを示しています。Entity (Jakarta Persistence API documentation)

実務ではテーブルに対応する永続化モデルとして扱われることが多いですが、常に1 Entity = 1テーブル = 1行ではありません。副テーブル、関連先のEntity、要素コレクションなどをマッピングできます。

DDDのEntityは識別子で同一性を判断します

DDDにおけるEntityは、属性ではなく識別子で同一性を判断する業務オブジェクトです。名前が変わってもユーザーIDが同じなら、同じユーザーとして扱います。

id = 100, name = Taro
        ↓ 名前変更
id = 100, name = Jiro

小さなCRUDではJPA EntityとドメインのEntityを同じクラスにしても成り立つ場合があります。一方で業務ロジックが複雑なら、永続化の都合を持つクラスと、業務上の振る舞いを持つクラスを分ける方が変更理由を分離しやすくなります。

DAOとRepositoryは抽象化する視点が違う

どちらも取得・保存に関わるため混同されます。しかし、主に何を隠すかが異なります。

DAOはData Access Objectの略です。データソースへの接続や取得・保存の詳細を隠します。対象はRDBMSだけでなく、LDAP、ファイル、外部サービスでも構いません。Core J2EE Patterns - Data Access Object - Oracle

public class UserDao {
    public UserRow findById(long id) {
        // SELECTを実行する
        return null;
    }
}

Repositoryは、ドメインとデータマッピング層の間に置かれ、ドメインオブジェクトをコレクションのように取得・保存するための窓口として説明されます。Repository - Martin Fowler

public interface UserRepository {
    Optional<User> findById(UserId id);
    void save(User user);
}

DAOはデータソース寄り、Repositoryは利用側・ドメイン寄りの抽象化と考えると区別しやすくなります。ただし、Spring Data JPAのJpaRepositoryはCRUDや検索機能を提供します。フレームワーク上のRepositoryという名前とDDDの概念を、名前だけで同一視しないことが重要です。Spring Data JPA - Core concepts

Modelは単独では役割を説明しません

ModelはDBモデル、ドメインモデル、APIモデル、ViewModel、機械学習モデルなどを指せる広い言葉です。

public class UserModel {
}

この名前だけでは、DBの表現なのか、API出力なのか、業務ロジックを持つのかは分かりません。UserResponse、UserViewModel、UserEntity、Userのように、境界または役割を表す名前の方が、読む人が責務を予測しやすくなります。

Value Object、POJO、Beanも分類の軸が違います

Value Objectは、識別子ではなく値そのものに意味を持たせるオブジェクトです。

public record EmailAddress(String value) {
    public EmailAddress {
        if (!value.contains("@")) {
            throw new IllegalArgumentException("@ を含む必要があります");
        }
    }
}

この例は@の有無だけを確認する簡略例であり、メールアドレス形式を完全に検証するものではありません。Value Objectは不変に設計すると、値による比較を安全に扱いやすくなります。

POJOは「普通のJavaオブジェクト」を表す慣用的な言葉で、DTOやEntityのような役割名とは分類の軸が異なります。Beanも文脈依存です。JavaBeansは規約を持つJavaオブジェクトを指し、Spring Beanはコンテナが管理するオブジェクトを指します。@Serviceを付けたクラスも、コンポーネントスキャンの対象であればSpring Beanとして登録されます。

MapperとServiceは名前だけで役割を決めない

Mapperは異なる表現を変換する責務です。例えば、永続化モデルをAPIの出力形式へ変換します。

public UserResponse toResponse(UserEntity entity) {
    return new UserResponse(entity.getId(), entity.getName(), entity.getEmail());
}

ただしMyBatisのMapperはSQL実行も担います。一般的な変換器としてのMapperとは別に理解する必要があります。

Serviceも、Application ServiceとDomain Serviceで役割が異なります。前者はユースケースの流れを制御し、後者は特定のEntityやValue Objectへ自然に置けないドメインロジックを担います。何でもXxxServiceへ入れると、責務が見えない大きなクラスになりやすいです。

同じUserを無条件に分ける必要はありません

層を分ける設計では、次のような変換を置くことがあります。

CreateUserRequest
    ↓
User
    ↓
UserEntity
    ↓
DB

これは一例です。単純なCRUDでDBの構造とAPIの構造がほぼ同じなら、全てを分けるコストが利益を上回ることがあります。一方でHTTP、業務ルール、永続化のどれかが独立して変わるなら、境界で表現を分ける価値が出ます。

クラス数を増やすことではなく、変更理由が異なるものを分けることを判断軸にします。

名前より責務を確認する

クラス名だけで設計を断定しないために、次の順番で見ると整理しやすくなります。

  1. 何を入力として受け取るか
  2. 何を返すか
  3. DBやHTTPなど、何に依存しているか
  4. 業務ロジックを持っているか
  5. どの層・境界から呼ばれているか

例えばUserRepositoryという名前でもSQL文字列を多く持てばDAOに近いかもしれません。逆にUserDaoという名前でも、ドメイン側の取得・保存を表現している場合があります。名前は読み始める手掛かりであり、責務の証明ではありません。

まとめ

  • DTOは境界でデータを運ぶための表現であり、API専用ではありません
  • JPA EntityとDDDのEntityは焦点が異なります
  • DAOはデータソースの詳細を、Repositoryはドメインの取得・保存を抽象化する見方が基本です
  • Model、Mapper、Service、Beanは文脈を確認しないと役割を断定できません
  • 分割の目的はクラス数を増やすことではなく、異なる変更理由を境界で分けることです

用語を完全に統一できないプロジェクトもあります。それでも、命名の意味、利用する層、依存の向きをREADMEや設計資料に短く残すと、参加した人が責務を予測しやすくなります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?