Анемичная модель и ООП для домена

Анемичный класс — класс, у которого есть только поля и нет (и не может быть) методов. ООП плохо работает с такими объектами. Это ключевой аргумент против бездумного применения ООП для бизнес-логики.

«В ~80% систем, которые массово пишутся в компаниях, объекты не имеют поведения. ООП хорош для объектов с поведением — но если поведения нет, нужно что-то другое», SR40bkadvew.

Что такое анемичная модель

Класс паспорта моделирует только информационный аспект:

class Passport {
  constructor(series, number, issued) {
    this.series = series;
    this.number = number;
    this.issued = issued;
  }
}
// никаких методов — нет и не может быть. Паспорты ничего не "делают"

Класс робота — другое дело:

class Robot {
  moveArm(dx, dy) { /* ... */ }
  stepForward { /* ... */ }
}
// есть состояние И поведение — это уже "правильный" ООП-объект

Где ООП работает идеально

Где
Системное программирование Stream, Socket, EventEmitter, Logger, Connection
GUI / графика компоненты, контролы, виджеты
Игры персонажи, монстры, инвентарь

Общее: объекты с поведением и состоянием, помещающиеся в одно адресное пространство.

«Объектная модель шикарно работает в одном адресном пространстве, когда можно дёрнуть метод у кого угодно, и все данные подняты в память».

Где ООП ломается

Почему
Бизнес-логика / домен данные не помещаются в RAM целиком
Распределённые системы объекты живут на разных машинах
Склад, медицина, такси, энергетика миллионы записей, нужно много машин

Товар, диагноз, документ, анализы — это данные, а не поведение. Список товаров не помещается в память — нужна СУБД.

Куда положить «списать просроченное»?

Классический аргумент:

  • К Product? Не поведение товара — товар ничего не делает.
  • К Worker? У него нет такого метода в модели домена.
  • Создать WriteOffService? Это уже transaction script — анти-ООП паттерн.

«Списание товаров — это не поведение товара. Это поведение работника склада. А его в модели нет».

Прослойка из паттернов вокруг СУБД

Чтобы вообще как-то склеить ООП-модель и БД, придумано множество паттернов:

  • Active Record — объект знает, как себя сохранить
  • Repository — объект ничего не знает, репо умеет CRUD
  • Data Mapper / ORM — слой между объектами и таблицами
  • Query Builder — типобезопасные запросы
  • Unit of Work — пакетные изменения

«Объектная модель порвана между память и БД, и нам нужно сюда привлекать всякие вещи типа Active Record, репозиториев, ORM, Query Builder. Это паттерны, которые штопают разрыв».

Иногда дополнительно — акторы, очереди сообщений для распределённости.

Альтернатива: процедуры + структуры

Для домена удобнее процедурный стиль:

// Стракты = записи БД, процедура работает с коллекцией
async function writeOffExpired(beforeDate) {
  return db.query(
    'DELETE FROM goods WHERE expires < $1 RETURNING *',
    [beforeDate]
  );
}
  • Данные (struct) и поведение (function) разделены
  • Часть логики в SQL, часть в JS
  • Не нужно придумывать, "чей" это метод

«Я больше всего люблю процедурное программирование. Что-то на классах — потому что удобно писать на классах системный код. Для домена — процедуры и Transaction Script», vCelCNfIiBo.

Учебные примеры обманчивы

«В учебниках работа идёт с двумя-тремя слониками, машинками и прочей странной живностью — она в памяти помещается. Реальные системы — склад, такси, медицина — не помещаются.»

Двух-трёхсущностные примеры дают ложное впечатление, что ООП хорошо моделирует домен. На большом домене этот трюк перестаёт работать.

Признаки анемичной модели

  • Все поля публичные, getters/setters просто читают/пишут
  • Нет инкапсуляции (бизнес-правил внутри)
  • Логика собрана в сервисах рядом
  • Класс заменим на Record<string, unknown> или TS interface

В таком случае честнее использовать TypeScript-структуру или функциональный стиль, а не имитировать ООП.

Мультипарадигменный подход

«Я использую и рекомендую мультипарадигменное программирование. Что-то на классах, что-то функциональное, что-то процедурное. Парадигма — под задачу».

Парадигма Когда
ООП (классы) системное, GUI, игры, объекты с поведением
Процедурное бизнес-логика, ETL, скрипты
Функциональное трансформации данных, чистые вычисления
Монадическое FP распределённость, эффекты, бесплатное масштабирование

Источники

Связанные темы