Domain Driven Design

Что это

Цитата с отличного объяснения на Stackoverflow:

DDD is about trying to make your software a model of a real-world system or process.

The idea is that you should be able to write down what the system does in a way that the domain expert can read it and verify that it is correct.

Разработка модели данных является одной из самых сложных задач в веб-разработке. Все дело в высокой связанности кода. Модель данных со временем обрастает кодом: views, templates, management commands. Менять структуру данных — значит переписывать и заново отлаживать существенную часть проекта. Занятие сложное и дорогое.

Названия моделей и полей берем из предметной области

Предположим, мы пишем сайт для агентства недвижимости и создали такую модель данных:

class Ads(db.Model):
    price = db.Column(db.Integer, index=True)
    description = db.Column(db.Text)
    show = db.Column(db.Boolean, index=True)

Прошло время, модель данных обросла кодом. А затем потребовались нововведения:

  1. пользователи и поисковые роботы могут видеть архивные объявления, все ради сохранения ссылочной массы;
  2. решено ввести правило премодерации и скрывать на сайте объявления не прошедшие модерацию.

Ситуация печальна. Флаг show больше не является гарантией сокрытия объявления. Его предназначение далеко не очевидно, что провоцирует на ошибки в коде и затрудняет отладку. Переименовывание же полей приведет к веерным правкам по всему коду проекта с последующим увлекательным дебагом.

Риелторы делят объявления на актуальные и устаревшие, но ничего не знают про видимые или невидимые.

Обобщить идею можно следующим образом.

В модели данных нельзя использовать названия и термины отсутствующие в предметной области

Еще один пример для наглядности. Тот же проект про недвижимость. У объявления появилось новое поле user:

class Ads(db.Model):
    user = Column(db.Integer, db.ForeignKey('user.id'))
    price = db.Column(db.Integer, index=True)
    description = db.Column(db.Text)

Стоит сразу задать себе вопрос "Какую роль играет этот user с точки зрения бизнеса?" Это автор объявления? Он собственник жилья или риелтор? А может, это модератор проверивший объявление? Что мы будем делать когда все эти роли придется отразить в модели данных, как не запутаться?

Риелторы не пользуются в своей повседневной работе термином "пользователь". Значит, и в модели данных его быть не должно.

Не придумывать новые сущности

Например, можно выдумать некую AutoDeal для брокеров. А потом долго и упорно им объяснять что это такое, зачем нужно, какое отношение имеет в валютным парам, Bid и Ask.

Как правило, новые термины не удается увязать с другими устоявшимися понятиями. Сделать это четко и непротиворечиво крайне сложно, даже экспертам это удается редко. Большинство новых понятий не выдерживают проверку временем и быстро выходят из обихода. Если не хочешь переписывать модель данных раз в полгода, то опирайся на устоявшиеся, надежные и всем понятные термины.

Также не стоит объединять несколько сущностей в одну, более удобную с программной точки зрения. Возникнут ровно те же проблемы. С точки зрения бизнеса эти сущности имеют разные свойства, ведь не зря их различают.

Не придумывать новые связи

Рассмотрим пример из другой области. Пиццерия создает себе сайт, а в базу данных кладет меню такого вида:

catalog = [
  {
    'title': 'Маргарита',
    'description': 'соус,сыр Моцарелла',
    'choices': [
      {'title': '30 см (450гр)', 'price': 360},
      {'title': '40 см (750гр)', 'price': 460}
    ],
  },
  // ... а еще 'Грибная', 'С ветчиной', 'Ветчина с грибами', всего 30 наименований
  // все они с идентичными choices: размером и ценой
]

Сразу бросается в глаза: у всей пиццы идентичные choices. Давай их объединим, и менеджерам не придется вбивать одни и те же цифры 100500 раз, они будут рады. Достаточно завести несколько сущностей Choice в базе и связать их отношением ManyToMany с Pizza. Получим такую картинy: 30 записей Pizza связаны с 2 записями Choice через промежуточную таблицу для связывания PizzaChoice.

Теперь цену на всю пиццу можно менять разом, буквально несколькими кликами мыши! Красота!

Посмотрим как дальше будут развиваться события. Проходит неделя и на пиццу #25 поднимают цену на 30%. Она готовится по необычному рецепту, такую никто из конкурентов не предлагает. И пусть стоимость ингредиентов та же, гурманы готовы платить больше. В базе данных создаем две новые записи Choice, не беда.

Проходит еще неделя, продажи падают — конкурент в соседнем квартале демпингует, приходится подстраиваться. Выбираем 10 самых популярных рецептов пиццы, снижаем цену на 10%, ищем дополнительные источники прибыли и партнеров. В базе данных появилось две дополнительные записи Choice.

И вот прошел год. В базе данных появились порядка 20 записей Choice, еще больше записей PizzaChoice. Понять смысл их связи и группировки не представляется возможным. Хаос победил. И нет никакого встроенного механизма рефакторинга, придется все сносить и вбивать цены заново. Печаль. Может, дело в неправильной группировке?

Суть проблемы в том что никто не знает как правильно группировать рецепты пиццы, какой критерий взять за основу. Более того, в природе такого критерия просто не существует. И никакой связи ManyToMany тоже не наблюдается, по крайней мере с ценами. А вот размер пиццы действительно может быть обусловлен общепринятыми стандартами.

Мы старались упростить работу менеджерам. Хотели как лучше, а получилось как всегда ©

Не упрощать связи

Если в реальном мире сущности связаны отношением ManyToMany, то не стоит их «упрощать» до OneToMany или OneToOne. Требования меняются всегда! Через полгода то, что «сейчас совершенно не нужно» может стать жизненно необходимым. А подобные изменения модели и логики даются дорогой ценой.

Что дает DDD

Возрастает стабильность модели данных

Ведь мы используем устоявшуюся терминологию, на нее можно положиться. Меньше рефакторинга, больше фич!

Программисту проще реализовать техзадание

В коде он использует ровно тот же набор терминов и связей между ними. Легко понять, легко отладить.