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 | Value Object |
|---|---|---|
| 同一性 | ID で判定 | 属性で判定 |
| 可変性 | 可変 | 不変 |
| 例 | User / Order | Money / DateRange |
| ライフサイクル | あり | なし |
| 依存 | 単独で意味 | Entity の一部 |
// 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 / Value Object をまとめる単位の最上位。トランザクション境界を定義。
Order(Aggregate Root)
├── OrderItem(Entity)×N
├── ShippingAddress(Value Object)
└── Payment(Value Object)
外部からは Aggregate Root のみ参照、内部 Entity は直接アクセス不可。
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 に切り出し。
export class OrderFactory {
static fromCart(cart: Cart, address: Address, payment: Payment): Order {
// 複雑なバリデーション + 変換
return Order.create({...});
}
}
| ORM | 形態 |
|---|---|
| Drizzle ORM | OSS、TypeScript ファースト |
| Prisma | OSS、スキーマ宣言的 |
| TypeORM | OSS、デコレータベース |
| MikroORM | OSS、Unit of Work |
| Kysely | OSS、Type-safe SQL builder |
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
└── ...
// 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
}
}
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(())
}
}
| 用語 | 違い |
|---|---|
| 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 に。