2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptにおけるエンティティ設計とドメイン思考:値オブジェクト・不変モデル・ユースケース分離戦略

Posted at

概要

「オブジェクトとは、データではなく意味を持った構造体である」
データベースのレコードをそのままJSオブジェクトに変換して“業務ロジックを書く”のでは、ビジネスの意味をコードに閉じ込めることはできない。

本稿では、エンティティ・値オブジェクト・不変性・責務分離・ユースケースとの接続戦略を軸に、ビジネスロジックとコード構造の一致を目指す設計思想を解説する。


1. ドメインモデルとエンティティの定義

  • エンティティ(Entity):一意のIDとライフサイクルを持つ「業務的な存在」
  • 値オブジェクト(Value Object):IDを持たず、属性によって等価性が決まる
// エンティティ例
class User {
  constructor({ id, name, email }) {
    this.id = id;
    this.name = name;
    this.email = email;
  }

  changeEmail(newEmail) {
    if (!newEmail.includes('@')) throw new Error('Invalid email');
    this.email = newEmail;
  }
}

→ ✅ IDを持ち、業務的に“振る舞う”データ


2. 値オブジェクトの設計

class Email {
  constructor(value) {
    if (!value.includes('@')) throw new Error('Invalid Email');
    this.value = value;
  }

  equals(other) {
    return this.value === other.value;
  }
}
  • 不変性を前提とする
  • ✅ ロジック内でのデータ検証と表現の責務を統一

→ 値に意味を持たせ、原始型では表せない“意図”をコード化


3. 不変性の維持と副作用の排除

// 値オブジェクトは再代入ではなく再生成
const newEmail = new Email('new@example.com');
user.changeEmail(newEmail.value);

→ ✅ 値を変えるのではなく、“新しい値で置き換える”


4. エンティティとユースケースの分離

❌ アプリケーション層でエンティティを破壊的に扱う

user.email = 'hoge@foo.com'; // ❌ 意味を持たない変更

✅ ドメイン層の振る舞いをユースケースが使う構図に

// usecases/changeUserEmail.js
export function changeUserEmail(userRepo, userId, newEmailStr) {
  const user = userRepo.findById(userId);
  const newEmail = new Email(newEmailStr);
  user.changeEmail(newEmail.value);
  userRepo.save(user);
}
  • ✅ ユースケースが振る舞いを**“呼び出す”**
  • ✅ 振る舞いはエンティティに“閉じ込める”

5. 永続化とドメインモデルの変換(データマッパー)

// infra/userMapper.js
export function toDomain(record) {
  return new User({
    id: record.id,
    name: record.name,
    email: record.email
  });
}

→ ✅ DBモデルとドメインモデルを“分離”
→ ✅ スキーマ変更に強く、責務が明確


6. 構造と設計例

/domain
  /user
    User.js          // エンティティ
    Email.js         // 値オブジェクト
/usecases
  changeUserEmail.js
/infra
  userRepository.js
  userMapper.js
  • 責務に応じて階層分離
  • ドメイン層にロジック、ユースケース層にアプリケーション操作、インフラ層にデータアクセス

設計判断フロー

① IDを持ち、状態変化がある? → エンティティで表現

② 単なる属性で等価性が決まる? → 値オブジェクトに切り出す

③ ドメイン知識が埋まっていないか? → エンティティ/VOに閉じ込める

④ DB構造と混ざっていないか? → 永続層とはマッパーで分離

⑤ ユースケースがドメインを“操作”していないか? → 振る舞いを呼び出す構造へ

よくあるミスと対策

❌ ドメインロジックがアプリケーション層に散乱

→ ✅ モデルに**“意味のある振る舞い”**を定義し閉じ込める


❌ 値検証や形式がロジック中にベタ書き

→ ✅ 値オブジェクトに移動し、明示的な意図を設計する


❌ DBレコード = モデルとし、ロジックが薄くなる

→ ✅ “モデル”は業務の振る舞いを担う構造であるべき


結語

オブジェクトとは、“フィールドとメソッドの集まり”ではない。
それは、**“意味を持ち、業務の振る舞いを制御する構造化された知識”**である。

  • エンティティに状態と振る舞いを閉じ
  • 値オブジェクトで構文と意味を一体化し
  • ユースケースはあくまで“操作するだけ”にとどめる

ドメインを設計するとは、現実の複雑さに名前を与え、責任を分けることである。
それがコードを意味あるものに変える第一歩である。

2
3
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
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?