open atlas
↑ К треку
Архитектурные паттерны ARCH · 06 · 02

Ubiquitous language

Ubiquitous language — общий словарь разработчиков и доменных экспертов внутри одного bounded context. Модель И ЕСТЬ язык: дрейф имён сигнализирует о неверной границе или отсутствующем концепте.

ARCH Senior ◷ 20 min
Уровень
ОсновыJuniorMiddleSenior

Sales-команда B2B-платформы называла это «Quote». В коде это было ProposalDraft. Таблица в базе данных называлась pending_orders. В продуктовой спецификации — «Pre-Order». CRM-плагин генерировал события DealCreated. Пять имён для одного бизнес-концепта — и каждый перевод открывал возможность для недопонимания. Разработчик, читающий баг-репорт о «квоте, которую не отправили», искал в коде Quote, ничего не находил, в конце концов обнаруживал ProposalDraft, пытался понять, не одно ли это с «Quote», читал спецификацию — там было «Pre-Order», находил таблицу pending_orders и в итоге спрашивал коллегу. Тридцать минут на поиск концепта. На той же неделе продакт-менеджер изменил правило: «Quote истекает через 30 дней». Разработчик поправил логику истечения в ProposalDraft. Но CRM-интеграция слушала событие DealCreated со своим автоматом состояний и не знала, что у ProposalDraft есть срок. Когда сделка истекала в коде, CRM по-прежнему показывала её как открытую. На ревью никто не заметил: ревьюер думал в терминах «Quote», а код был в терминах ProposalDraft. Термин Эванса для починки обманчиво прост: «ubiquitous language». Но простота вводит в заблуждение. Это не соглашение об именовании. Это обязательство: язык, на котором говорят на встречах с доменными экспертами, — это в точности тот язык, который записан в коде, базе данных, событиях, тестах и документации — без слоя перевода нигде.

Что ubiquitous language означает на самом деле

«Ubiquitous» означает «встречающийся повсюду». Суть позиции Эванса: язык должен присутствовать — без перевода — в:

  • Разговорах между разработчиками и доменными экспертами
  • Коде: именах классов, методов, переменных, событий
  • Схеме базы данных: именах таблиц, столбцов
  • Тестах: описаниях тестов, именах тестовых данных
  • Документации и спецификациях
  • Метках пользовательского интерфейса (там, где они отражают доменные концепты)

Когда слово из разговора с доменным экспертом отличается от слова в коде, на границе каждого разговора происходит перевод. Переводы порождают ошибки. Они также маскируют проблемы модели: разработчик, использующий ProposalDraft, перестаёт задаваться вопросом, применимы ли правила «Quote» доменного эксперта, — потому что имена не совпадают.

Ubiquitous language не изобретается разработчиками и не объясняется потом доменным экспертам. Он вырабатывается совместно. Доменные эксперты используют естественный язык неточно: «заказ подан» может означать многое. Процесс построения ubiquitous language заставляет обе стороны быть точными. «Заказ подан» становится: «Sales Order переходит в состояние SubmittedForApproval, когда менеджер нажимает Confirm и все обязательные позиции имеют согласованную цену». Эта точность входит в код как имя метода (submitForApproval()), значение enum (OrderStatus.SUBMITTED_FOR_APPROVAL) и доменное событие (SalesOrderSubmittedForApproval).

Почему это работает

Почему «модель И ЕСТЬ язык», а не «модель реализует язык»? Потому что если между ними есть разрыв — язык говорит «Quote», а модель говорит ProposalDraft — у вас два артефакта для поддержки. Каждый раз, когда язык эволюционирует (доменные эксперты открывают новый концепт или уточняют существующий), нужно обновлять и модель, и языковой артефакт. Когда они одно и то же, открытие концепта немедленно становится переименованием класса, новым методом или новым доменным событием. Именно поэтому Эванс настаивает на «ubiquitous» — «вездесущем», а не «согласованном» или «задокументированном»: он должен быть везде, а не в глоссарии, живущем отдельно от кода.

Модель как язык: как это выглядит в коде

В B2B-платформе Sales-контекст вырабатывает ubiquitous language вокруг концептов: Quote, LineItem, NegotiatedPrice, AccountExecutive, QuoteExpiry, QuoteSubmission. Код отражает это буквально:

  • Класс Quote с методами addLineItem(), negotiate(), submitForApproval(), expire()
  • Value object NegotiatedPrice (не BigDecimal с комментарием)
  • Доменное событие QuoteSubmittedForApproval (не StatusUpdated с полем в payload)
  • Репозиторий QuoteRepository с методом findExpiringBefore(date: LocalDate)

Ни одно из этих имён не требует словаря. Доменный эксперт, читающий тест quote_expires_when_no_approval_within_30_days, понимает его немедленно. Когда доменный эксперт меняет правило на «Quote от enterprise-клиентов истекает через 60 дней», разработчик точно знает, какой класс и какой метод менять.

Дрейф имён как сигнал

Самое ценное в ubiquitous language — не то, что он говорит, когда работает, а то, что он говорит, когда ломается. Когда язык начинает дрейфовать, с моделью что-то не так.

Сигнал 1: слову начинают требоваться уточнения. Когда разработчики начинают говорить «Sales Order» и «Billing Order», чтобы различать две вещи, которые оба называют «Order» — они обнаружили, что «Order» означает разное в двух местах. Это сигнал о границе bounded context, а не проблема именования. Решение — не переименовать оба варианта, а поместить каждый в свой контекст, где «Order» однозначно обозначает концепт этого контекста.

Сигнал 2: один концепт получает разные имена в разных файлах. ProposalDraft в доменной модели, Quote в сервисном слое, pending_order в базе данных. Это провал ubiquitous language: модель не совпадает с языком, на котором реально говорит команда.

Сигнал 3: доменные эксперты используют слово, которого нет в коде. Если доменный эксперт говорит об «approval escalation», а в коде нет класса ApprovalEscalation, нет метода escalate(), нет события QuoteEscalated — этот концепт живёт только в головах экспертов и в куче условий, разбросанных по сервисным методам. Модель упускает концепт, который реально существует в домене.

lesson.inset.note

Ubiquitous language ограничен одним bounded context. Язык Billing-контекста отделён от языка Sales-контекста — намеренно. «Customer» в Sales означает нечто иное, чем в Billing, — и это нормально. Каждый контекст имеет свой точный язык. Ошибка — пытаться построить один унифицированный язык для всех контекстов: это порождает ту же проблему God-объекта на 47 полей, но на уровне словаря. Язык внутри контекста точен; перевод между контекстами явен.

Рефакторинг к языку

Команда, у которой есть разрыв ubiquitous language, имеет задачу в бэклоге: переименовать ProposalDraft в Quote, переименовать pending_orders в quotes, переименовать события StatusUpdated в QuoteSubmittedForApproval и QuoteExpired. Это не косметика. Каждое переименование — исправление модели, снижающее стоимость перевода для каждого будущего разговора с доменным экспертом.

Дисциплина такова: когда доменный эксперт использует новый термин в разговоре — следующий шаг добавить его в модель, не в глоссарий, не в комментарий, а в код. Если термин раскрывает отсутствующий концепт — создать класс. Если раскрывает отсутствующий переход состояния — добавить метод. Если раскрывает отсутствующее событие — добавить тип события. Модель и язык эволюционируют вместе.

Викторина

Разработчик переименовывает класс `ProposalDraft` в `Quote` после разговора с доменным экспертом. Тимлид возражает: «Это просто косметическое переименование — оно не добавляет бизнес-ценности и рискует внести баги». Что не так с этой формулировкой?

Викторина

На обзоре спринта доменный эксперт говорит: «Когда крупный enterprise-клиент размещает заказ, мы сначала делаем кредитную проверку, а для мелких клиентов просто одобряем автоматически». Разработчик смотрит в код и находит `if (customer.tier == 'ENTERPRISE') { creditService.check(order); }`, разбросанный по трём сервисным методам. Что это говорит о модели?

Викторина

У команды два bounded context: Sales и Billing. В Sales концепт называется «Quote» с жизненным циклом Draft → Submitted → Approved → Expired. В Billing тот же документ называется «Invoice» с жизненным циклом Pending → Sent → Paid → Overdue. Новый разработчик говорит: «Это явно один и тот же документ — нужно объединить в одну модель, чтобы не дублировать». Почему это неверно?

Вспомните перед уходом
  1. 01
    Что означает «ubiquitous» в «ubiquitous language» и почему важен именно этот выбор слова?
  2. 02
    Каковы три сигнала дрейфа ubiquitous language и что каждый из них означает?
  3. 03
    Почему ubiquitous language должен быть ограничен одним bounded context?
Итог

Пять имён для одного концепта из вступительной истории — Quote, ProposalDraft, pending_order, Pre-Order, DealCreated — были не провалом соглашения об именовании. Это был провал модели: общий язык не был согласован, поэтому каждая команда изобрела свой термин, и каждая точка интеграции требовала мысленного перевода.

Ubiquitous language решает это, делая словарь доменного эксперта словарём кода — без перевода нигде. Имена классов, методов, событий, описания тестов и столбцы баз данных используют те же термины, что появляются в разговорах с доменными экспертами. Когда доменное правило меняется, разработчик немедленно находит нужный код, потому что имена совпадают.

Язык — мощный диагностический инструмент. Когда слово требует уточнений — граница bounded context стоит не там. Когда один концепт имеет разные имена в разных файлах — модель не совпадает с пониманием команды. Когда термин живёт в разговорах с экспертами, но не в коде — в модели отсутствует концепт.

Следующий урок рассматривает, как применять согласованность модели внутри bounded context — в частности, паттерн агрегата, определяющего транзакционную границу вокруг кластера связанных объектов.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 4 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

Примени это

Примени этот урок в реальном проекте.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.