ソフトウェア
上級

エンティティ(エンティティ)

DDD(ドメイン駆動設計)/ Clean Architecture でビジネスドメインの実体を表すオブジェクト。一意な ID で同一性を持ち、ライフサイクル + ビジネスルールをカプセル化する。

0 回閲覧
0 いいね
2026/5/2 更新
関連タグ
DDD
Clean Architecture
TypeScript
Rust
Java

概要

エンティティ(Entity)は、DDD(ドメイン駆動設計)/ Clean Architecture でビジネスドメインの実体を表すオブジェクトを指す。一意な ID(Identifier)で同一性を持ち、属性(フィールド)の変化に関わらず「同じもの」と認識される。Order / User / Product / Article 等のビジネスドメインの中核要素を、ライフサイクル + ビジネスルールをカプセル化したクラス / 構造体として実装する。Eric Evans の DDD 書籍(2003)で確立、2026 年現在 TypeScript / Rust / Java / Kotlin / Go / C# 等の主要言語で標準的に採用される設計パターン。

Entity の特徴(DDD / Clean Architecture)

  • 一意 ID: UUID / ULID / Snowflake ID 等
  • 同一性: ID が同じなら属性違っても同一エンティティ
  • 可変性: 状態変化が許容される(Value Object との対比)
  • ビジネスメソッド: ロジックをエンティティ内に集約
  • 不変条件: 常に成立すべきビジネスルール
  • 永続化非依存: DB スキーマと独立

Entity vs Value Object

項目EntityValue Object
同一性ID で判定属性で判定
可変性可変不変
例User / OrderMoney / DateRange
ライフサイクルありなし
依存単独で意味Entity の一部

TypeScript Entity 実装例(Clean Architecture)

// Entity
export class Order {
  private constructor(
    public readonly id: OrderId,
    private items: OrderItem[],
    private status: OrderStatus,
    private readonly createdAt: Date,
  ) {}

  static create(params: CreateOrderParams): Order {
    if (params.items.length === 0) {
      throw new DomainError("注文には最低 1 商品が必要");
    }
    return new Order(
      OrderId.generate(),
      params.items,
      OrderStatus.Pending,
      new Date(),
    );
  }

  // ビジネスメソッド
  cancel(): void {
    if (this.status !== OrderStatus.Pending) {
      throw new DomainError("Pending のみキャンセル可");
    }
    this.status = OrderStatus.Cancelled;
  }

  // 計算メソッド(Value Object 返す)
  totalAmount(): Money {
    return this.items.reduce(
      (sum, item) => sum.add(item.subtotal()),
      Money.zero(),
    );
  }

  // 永続化用(ID + 必要最小限)
  toJSON() {
    return {
      id: this.id.value,
      items: this.items.map(i => i.toJSON()),
      status: this.status,
      createdAt: this.createdAt.toISOString(),
    };
  }
}

Entity の主要パターン

Aggregate Root(集約ルート)

複数 Entity / Value Object をまとめる単位の最上位。トランザクション境界を定義。

Order(Aggregate Root)
├── OrderItem(Entity)×N
├── ShippingAddress(Value Object)
└── Payment(Value Object)

外部からは Aggregate Root のみ参照、内部 Entity は直接アクセス不可。

Repository パターン

export interface OrderRepository {
  findById(id: OrderId): Promise<Order | null>;
  save(order: Order): Promise<void>;
  delete(id: OrderId): Promise<void>;
}

Domain 層は Interface 定義のみ、Infrastructure 層が PostgreSQL / MongoDB 等で実装。

Factory パターン

複雑な初期化ロジックを Factory に切り出し。

export class OrderFactory {
  static fromCart(cart: Cart, address: Address, payment: Payment): Order {
    // 複雑なバリデーション + 変換
    return Order.create({...});
  }
}

ORM / DB マッピング(2026 年)

TypeScript / Node.js

ORM形態
Drizzle ORMOSS、TypeScript ファースト
PrismaOSS、スキーマ宣言的
TypeORMOSS、デコレータベース
MikroORMOSS、Unit of Work
KyselyOSS、Type-safe SQL builder

自作.com(jisaku.com)の Entity 例

apps/jisakucom-api/src/domain/
├── entities/
│   ├── product.entity.ts(34 ファイル全 Entity)
│   ├── faq.entity.ts
│   ├── pc-template.entity.ts
│   └── article.entity.ts
└── value-objects/
    ├── price.vo.ts
    ├── rating.vo.ts
    └── ...

Java / Kotlin

// Kotlin Data Class(Value Object)+ Entity
data class OrderId(val value: String)
data class Money(val amount: BigDecimal, val currency: String)

class Order(
    val id: OrderId,
    private val items: MutableList<OrderItem>,
    private var status: OrderStatus,
) {
    fun cancel() {
        require(status == OrderStatus.Pending) { "Pending のみキャンセル可" }
        status = OrderStatus.Cancelled
    }
}

Rust

pub struct Order {
    pub id: OrderId,
    items: Vec<OrderItem>,
    status: OrderStatus,
}

impl Order {
    pub fn new(items: Vec<OrderItem>) -> Result<Self, DomainError> {
        if items.is_empty() {
            return Err(DomainError::EmptyItems);
        }
        Ok(Order {
            id: OrderId::new(),
            items,
            status: OrderStatus::Pending,
        })
    }

    pub fn cancel(&mut self) -> Result<(), DomainError> {
        if self.status != OrderStatus::Pending {
            return Err(DomainError::InvalidStatus);
        }
        self.status = OrderStatus::Cancelled;
        Ok(())
    }
}

アンチパターン

  • Anemic Domain Model: ロジック空っぽ、データ持ちのみ
  • God Class: 1 Entity が数千行、責務不明
  • Public Setter 乱用: 不変条件破綻
  • DB スキーマ駆動: テーブル = Entity と直結
  • 循環依存: Entity 同士が双方向参照

関連用語との違い

用語違い
Value Object不変、属性で同一判定
AggregateEntity + VO の集合
DTO(Data Transfer Object)データ運搬専用、ロジックなし
Domain ServiceEntity 跨ぎロジック

よくある質問

Q1: ORM の Model クラスと Entity、同じ? A: 別概念。ORM Model は DB テーブルと 1:1 マッピング。Entity は Domain 層のビジネスオブジェクトで永続化非依存。実装上は同一クラスにすることもあるが、純粋 DDD では分離推奨。

Q2: 個人開発で Entity / Value Object 分離は過剰? A: 学習目的なら有効、本気の SaaS は推奨。MVP プロトタイプは MVC で十分。長期保守 + チーム拡大時に Clean Architecture の真価発揮。

Q3: Aggregate のサイズ目安は? A: 1-10 Entity 程度が王道。大きすぎると性能 + ロック競合問題、小さすぎるとトランザクション境界不明。「同時に変更すべき範囲」を Aggregate に。

まとめ

  • DDD / Clean Architecture の ビジネスドメイン実体オブジェクト
  • 2026 年は TypeScript / Rust / Java / Kotlin で標準的設計
  • Aggregate Root + Repository + Value Object が三大パターン
この記事について
カテゴリーソフトウェア
難易度上級
作成日2026/5/2