← Назад к вопросам

Что такое DDD (Domain-Driven Design)?

3.0 Senior🔥 72 комментариев
#Архитектура и паттерны

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое DDD (Domain-Driven Design)

Domain-Driven Design (DDD) — это методология проектирования программного обеспечения, которая фокусируется на бизнес-домене (области деятельности) как центре разработки. DDD помогает создавать системы, которые точно отражают реальные бизнес-процессы.

Основная идея

Вместо того, чтобы начинать с технологий ("давайте используем React и Redux"), DDD говорит:

Начни с понимания бизнеса!

Бизнес-требования → Язык (Ubiquitous Language) → Архитектура → Технологии

Ключевые концепции DDD

1. Ubiquitous Language (Единый язык)

Технические специалисты, продакт-менеджеры и бизнес должны использовать один словарь:

// Плохо: используем технические термины
const updateUserData = (user) => {...}; // Что такое "data"?

// Хорошо: используем язык бизнеса
const enrollUserInCourse = (user, course) => {...}; // Ясно: пользователь записывается на курс
const submitQuestionAnswer = (user, question, answer) => {...}; // Понятно сразу

Такой язык называется Ubiquitous Language — он общий для всей команды.

2. Domain (Домен)

Домен — это область деятельности компании. Например:

  • Для эдтеха: обучение, вопросы, экзамены, студенты
  • Для e-commerce: товары, заказы, доставка, платежи
  • Для SaaS: подписки, аккаунты, биллинг

3. Bounded Context (Ограниченный контекст)

Большие системы делятся на подмножества (контексты), каждый со своим языком и моделями:

Онлайн-школа
├── Контекст "Обучение" (учебные материалы, прогресс, оценки)
├── Контекст "Платежи" (транзакции, подписки, счета)
├── Контекст "Аутентификация" (логин, сессии, права доступа)
└── Контекст "Аналитика" (статистика, отчеты)

В каждом контексте могут быть разные определения одного сущности. Например:

  • В контексте "Обучение" "User" имеет progress и answers
  • В контексте "Платежи" "User" имеет subscription и invoices

4. Entity (Сущность)

Сущность — это объект, который имеет уникальный идентификатор и жизненный цикл:

class User {
  constructor(id, name, email) {
    this.id = id; // Уникальный идентификатор
    this.name = name;
    this.email = email;
  }
  
  // Сущность может менять свое состояние
  enrollCourse(course) {
    if (this.canEnroll()) {
      this.enrolledCourses.push(course);
    }
  }
}

5. Value Object (Объект-значение)

Value Object — это объект, который определяется своими значениями, а не идентификатором:

// Value Object: Email
class Email {
  constructor(value) {
    if (!this.isValid(value)) {
      throw new Error("Invalid email");
    }
    this.value = value;
  }
  
  isValid(email) {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }
  
  equals(other) {
    return this.value === other.value; // Сравнение по значению
  }
}

const email1 = new Email("user@example.com");
const email2 = new Email("user@example.com");
console.log(email1.equals(email2)); // true — одинаковые значения

6. Aggregate (Агрегат)

Агрегат — это группа связанных сущностей и value objects, которые работают как одно целое:

class Course {
  constructor(id, title) {
    this.id = id;
    this.title = title;
    this.lessons = []; // Сущность
    this.students = []; // Сущность
  }
  
  addLesson(lesson) {
    this.lessons.push(lesson);
  }
  
  enrollStudent(student) {
    this.students.push(student);
  }
}

// Course агрегирует Lesson и Student
const course = new Course(1, "JavaScript");
course.addLesson(new Lesson(1, "Основы"));
course.enrollStudent(student);

7. Repository (Хранилище)

Repository — это абстракция для доступа к данным:

class UserRepository {
  async findById(id) {
    // Может быть из БД, API, кэша...
    return db.query("SELECT * FROM users WHERE id = ?", [id]);
  }
  
  async save(user) {
    return db.query("INSERT INTO users VALUES (...)");
  }
}

// Использование
const userRepo = new UserRepository();
const user = await userRepo.findById(123);

8. Service (Сервис)

Service — это операция, которая не принадлежит сущности, но отражает бизнес-логику:

class EnrollmentService {
  async enrollUserInCourse(userId, courseId) {
    const user = await this.userRepository.findById(userId);
    const course = await this.courseRepository.findById(courseId);
    
    if (!user.canAfford(course.price)) {
      throw new Error("Insufficient funds");
    }
    
    user.enrollCourse(course);
    await this.userRepository.save(user);
  }
}

DDD в фронтенде (React)

DDD не только для бэкенда. Фронтенд может тоже организовать по доменам:

src/
├── domains/
│   ├── users/
│   │   ├── types.ts (User, UserProfile)
│   │   ├── services.ts (UserService)
│   │   └── hooks.ts (useUser, useUserProfile)
│   ├── courses/
│   │   ├── types.ts (Course, Lesson)
│   │   ├── services.ts (CourseService)
│   │   └── hooks.ts (useCourse, useLessons)
│   └── enrollment/
│       ├── types.ts (Enrollment)
│       ├── services.ts (EnrollmentService)
│       └── hooks.ts (useEnrollment)
├── components/ (UI слой)
└── pages/ (маршруты)

Пример: Онлайн-школа (DDD подход)

// Domain слой
type UserId = string & {readonly __brand: "UserId"};
type CourseId = string & {readonly __brand: "CourseId"};

interface User {
  id: UserId;
  name: string;
  email: string;
  enrolledCourses: CourseId[];
}

interface Course {
  id: CourseId;
  title: string;
  price: number;
  lessons: Lesson[];
}

interface Enrollment {
  userId: UserId;
  courseId: CourseId;
  enrolledAt: Date;
  progress: number;
}

// Application слой
class EnrollmentService {
  async enrollUserInCourse(
    userId: UserId,
    courseId: CourseId
  ): Promise<Enrollment> {
    const user = await this.userRepo.findById(userId);
    const course = await this.courseRepo.findById(courseId);
    
    if (user.enrolledCourses.includes(courseId)) {
      throw new Error("Already enrolled");
    }
    
    const enrollment = {
      userId,
      courseId,
      enrolledAt: new Date(),
      progress: 0
    };
    
    return this.enrollmentRepo.save(enrollment);
  }
}

// Presentation слой (React)
function CourseEnrollButton({ courseId, userId }: Props) {
  const [isLoading, setIsLoading] = useState(false);
  const enrollmentService = useEnrollmentService();
  
  const handleEnroll = async () => {
    setIsLoading(true);
    try {
      await enrollmentService.enrollUserInCourse(
        userId as UserId,
        courseId as CourseId
      );
      showSuccess("Enrolled successfully!");
    } catch (error) {
      showError(error.message);
    } finally {
      setIsLoading(false);
    }
  };
  
  return <button onClick={handleEnroll}>Enroll</button>;
}

Преимущества DDD

  1. Понимание бизнеса — команда четко понимает, что и зачем делает
  2. Масштабируемость — легче разделить работу между разработчиками
  3. Гибкость — изменения в бизнесе отражаются в коде
  4. Качество — меньше ошибок благодаря четкой структуре
  5. Коммуникация — разработчики и бизнес говорят на одном языке

Заключение

DDD — это не просто архитектурный паттерн, а философия разработки, которая ставит бизнес-домен в центр всего. Это помогает создавать системы, которые действительно решают реальные проблемы.