エンティティ(エンティティ)
DDD(ドメイン駆動設計)/ Clean Architecture でビジネスドメインの実体を表すオブジェクト。一意な ID で同一性を持ち、ライフサイクル + ビジネスルールをカプセル化する。
概要
エンティティ(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
| 項目 | Entity | Value Object |
|---|---|---|
| 同一性 | ID で判定 | 属性で判定 |
| 可変性 | 可変 | 不変 |
| 例 | User / Order | Money / 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 ORM | OSS、TypeScript ファースト |
| Prisma | OSS、スキーマ宣言的 |
| TypeORM | OSS、デコレータベース |
| MikroORM | OSS、Unit of Work |
| Kysely | OSS、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 | 不変、属性で同一判定 |
| Aggregate | Entity + VO の集合 |
| DTO(Data Transfer Object) | データ運搬専用、ロジックなし |
| Domain Service | Entity 跨ぎロジック |
よくある質問
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 が三大パターン