🫠

DDDってなんなの?

に公開

DDD(ドメイン駆動設計)入門

初めまして!学生エンジニアの kazu です。
今回は、インターン先でよく見かけた DDD(Domain-Driven Design / ドメイン駆動設計) について整理してみます。

最初は「何それ…?」と思いましたが、実務やAIとの壁打ちで少しずつ理解できてきました。
この記事では、躓きやすいポイントや自分の学びを交えつつ、DDDの本質を解説します。


そもそもDDDって何?

DDD(Domain Driven Design) とは、
ビジネス(ドメイン)の意味を中心にコードを設計する考え方」です。

  • 技術やアーキテクチャではなく、業務ルールを正しく表現することが目的
  • 「クリーンアーキテクチャの層構造」などはDDDの本質ではなく、あくまで 実装手段 にすぎません

Value Object(値オブジェクト)

Value Object は、一意なIDを持たず、値で同一性を判断するオブジェクトです。
ビジネスルールを型に閉じ込めるために使います。

例:Email

type Email struct {
    value string
}

func NewEmail(v string) (Email, error) {
    // 簡易的なメールアドレス形式チェック
    re := regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
    if !re.MatchString(v) {
        return Email{}, errors.New("メールアドレスの形式が不正です")
    }
    return Email{value: v}, nil
}

func (e Email) String() string {
    return e.value
}

ポイント:

値にドメイン制約(ここではメール形式)を付ける

同じ値なら同一とみなす

制約がない場合はプリミティブ型(string等)で十分

Entity(エンティティ)

  • Entity は 一意なIDを持ち、状態を持つオブジェクト です。

  • 同じ属性でもIDが違えば別のEntity

  • Value Object をフィールドに持つことも多い

  • 複数属性にまたがるドメインルールを表現できる

例:User

type User struct {
    id        uuid.UUID
    name      string
    email     Email
    createdAt time.Time
    updatedAt time.Time
}

// コンストラクタ
func NewUser(name string, email Email) (User, error) {
    now := time.Now()
    user := User{
        id:        uuid.New(),
        name:      name,
        email:     email,
        createdAt: now,
        updatedAt: now,
    }

    if err := user.Validate(); err != nil {
        return User{}, err
    }

    return user, nil
}

// 複数フィールドにまたがるバリデーション
func (u User) Validate() error {
    if u.createdAt.After(u.updatedAt) {
        return errors.New("作成日時が更新日時より後です")
    }
    return nil
}

ポイント:

Value Object は単一値の制約を担当

Entity は IDと状態、複数属性間の制約を担当

Domain Service(ドメインサービス)

Domain Service は、Entity や Value Object 単体では表現しきれないドメインルール を定める場所です。
特に、複数の Entity や Value Object が絡む場合に使います。
例:ユーザー名の一意性チェック

  • 単一の User Entity 内ではチェックできない

  • 他の User Entity を参照する必要があるため、Domain Service で実装

type UserService struct {
    repo UserRepository
}

func (s UserService) IsNameUnique(name string) (bool, error) {
    exists, err := s.repo.ExistsByName(name)
    if err != nil {
        return false, err
    }
    return !exists, nil
}

ポイント:

複数オブジェクトにまたがるビジネスルールを担当

リポジトリにアクセスすることが多いが、永続化はサービスの責務ではなく、あくまで補助

まとめ

  • DDDの本質 = ドメイン(ビジネス)を正しくモデル化すること

  • Value Object = 値の制約と意味を表す

  • Entity = 一意なIDと状態を持つオブジェクト

  • Domain Service = 複数オブジェクトにまたがるビジネスルール

  • 層構造やリポジトリ = DDDを支える手段であり、本質ではない

終わりに

この記事には私の思想も含まれています。間違っていたら教えて頂けると助かります🙇

Discussion